学习Vue.js
0. ES6补充
0.1 var
没有块级作用域
在
if
和for
中会出错
<script>
{
var name = 'Hello Vue';
console.log(name);
}
console.log(name);
</script>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
var btns = document.getElementsByTagName("button");
for (var i =0;i<btns.length;++i) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击')
})
}
</script>
0.2 const
的使用
- 使用
const
修饰的标识符为常量, 不可以再次赋值. - 当我们修饰的标识符不会被再次赋值时, 就可以使用
const
来保证数据的安全性. -
建议: 在ES6开发中优先使用
const
, 只有需要改变某一个标识符的时候才使用let.
0.3 ES6
对象字面量增强写法
-
属性的增强写法
// 1. 属性的增强写法 const name = 'why'; const age = 18; const height = 1.88; // ES5的写法 const obj1 = { name: name, age: age, height: height, } // ES6的写法 const onj2 = { name, age, height }
-
方法的属性增强
/*2. 方法的属性增强*/ // ES5的写法 const ob1 = { run: function () { } } // ES6的写法 const ob2 = { run() { } }
1. 介绍
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。
- 渐进式意味着可以将 Vue 作为应用的一部分嵌入其中,带来更丰富的交互体验。
2. Vue.js初体验
2.1 第一个Vue.js程序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<div id="app"></div>
<body>
<script src="../js/vue.js"></script>
<script>
// let定义变量 const定义常量
// 编程范式:声明式编程
/*好处:数据与界面完全分离*/
let app = new Vue({
el: '#app', // 定义要挂载的元素
data: { // 定义数据
message: 'Hello Vue.js',
}
})
</script>
</body>
</html>
2.2 Vue列表的展示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p></p>
<ul>
<li v-for="movie in movies"></li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
let app = new Vue({
el: "#app",
data: {
message: 'Hello Vue.js',
movies: ['星际穿越', '大话西游', '少年派', '盗梦空间'],
}
})
</script>
</body>
</html>
2.3 计数器
- 实现功能:小的计数器
- 点击
+
计数器加1 - 点击
-
计数器减1
- 点击
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>当前记数: </h2>
<!--方法一:-->
<!-- <button v-on:click="++counter">+</button>
<button v-on:click="--counter">-</button>-->
<!--方法二:-->
<button v-on:click="add"></button>
<button v-on:click="sub"></button>
</div>
<script src="../js/vue.js"></script>
<script>
let app = new Vue({
el: "#app",
data: {
counter: 0,
},
methods: {
add: function () {
console.log("add被执行");
this.counter++;
},
sub: function () {
console.log("sub被执行");
this.counter--;
}
}
})
</script>
</body>
</html>
3. Vue中的MVVM
3.1 什么是MVVM
MVVM
是 Model–view–viewmodel
的缩写,是一种软件架构模式。
MVVM
有助于将 图形用户界面 的开发与 业务逻辑 或 后端逻辑(数据模型)的开发开来,这是通过 置标语言 或GUI代码实现的。MVVM
的视图模型是一个值转换器,这意味着视图模型负责从模型中暴露(转换) 数据对象 ,以便轻松管理和呈现对象。在这方面,视图模型比视图做得更多,并且处理大部分视图的显示逻辑。视图模型可以实现 中介者模式 ,组织对视图所支持的 用例 集的后端逻辑的访问。
- 模型
- 模型是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。
- 视图
- 就像在
MVC
和MVP
模式中一样,视图是用户在屏幕上看到的结构、布局和外观(UI)
- 就像在
- 视图模型
- 视图模型是暴露公共属性和命令的视图的抽象。MVVM没有MVC模式的控制器,也没有MVP模式的presenter,有的是一个绑定器。在视图模型中,绑定器在视图和数据绑定器之间进行通信。
- 绑定器
- 声明性数据和命令绑定隐含在
MVVM
模式中。在Microsoft解决方案堆中,绑定器是一种名为XAML
的标记语言。 绑定器使开发人员免于被迫编写样板式逻辑来同步视图模型和视图。在微软的堆之外实现时,声明性数据绑定技术的出现是实现该模式的一个关键因素。
- 声明性数据和命令绑定隐含在
3.2 Vue中的MVVM
4. Vue的生命周期
可以看到在 vue
整个生命周期中会有很多 **钩子函数 **提供给我们在 vue
生命周期不同的时刻进行操作:
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<h1></h1>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Vue的生命周期'
},
beforeCreate: function() {
console.group('------beforeCreate创建前状态------');
console.log("%c%s", "color:red" , "el : " + this.$el); //undefined
console.log("%c%s", "color:red","data : " + this.$data); //undefined
console.log("%c%s", "color:red","message: " + this.message)
},
created: function() {
console.group('------created创建完毕状态------');
console.log("%c%s", "color:red","el : " + this.$el); //undefined
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
beforeMount: function() {
console.group('------beforeMount挂载前状态------');
console.log("%c%s", "color:red","el : " + (this.$el)); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
mounted: function() {
console.group('------mounted 挂载结束状态------');
console.log("%c%s", "color:red","el : " + this.$el); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
beforeUpdate: function () {
console.group('beforeUpdate 更新前状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
updated: function () {
console.group('updated 更新完成状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
beforeDestroy: function () {
console.group('beforeDestroy 销毁前状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
destroyed: function () {
console.group('destroyed 销毁完成状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message)
}
})
</script>
</html>
-
在
beforeCreate
和created
钩子函数之间的生命周期- 在这个生命周期之间,进行初始化事件,进行数据的观测,可以看到在
created
的时候数据已经和data
属性进行绑定(放在data
中的属性当值发生改变的同时,视图也会改变)。 - 注意看下:此时还是没有
el
选项
- 在这个生命周期之间,进行初始化事件,进行数据的观测,可以看到在
-
created
钩子函数和beforeMount
间的生命周期-
首先会判断对象是否有el选项。如果有的话就继续向下编译,如果没有el选项,则停止编译,也就意味着停止了生命周期,直到在该vue实例上调用vm.$mount(el)。
-
如果注释掉代码
el: '#app'
, 运行可以到created
的时候就停止了: -
如果我们在后面继续调用
vm.$mount(el)
, 可以发现代码继续向下执行了 -
我们往下看,
template
参数选项的有无对生命周期的影响。-
如果
vue
实例对象中有template
参数选项,则将其作为模板编译成render
函数。 -
如果没有
template
选项,则将外部HTML
作为模板编译。 -
可以看到
template
中的模板优先级要高于outer HTML
的优先级。 -
举例:在HTML结构中增加了一串html,在vue对象中增加了template选项:
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue生命周期学习</title> <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script> </head> <body> <div id="app"> <!--html中修改的--> <h1></h1> </div> </body> <script> var vm = new Vue({ el: '#app', template: "<h1></h1>", //在vue配置项中修改的 data: { message: 'Vue的生命周期' } </script> </html>
-
执行后的结果可以看到在页面中显示的是:
-
将
vue
对象中template
的选项注释掉后打印如下信息: -
在vue对象中还有一个render函数,它是以createElement作为参数,然后做渲染操作,而且我们可以直接嵌入JSX.
-
new Vue({ el: '#app', render: function(createElement) { return createElement('h1', 'this is createElement') } })
-
-
所以综合排名优先级:
render
函数选项 >template
选项 > outer HTML
.
-
-
-
beforeMount
和mounted
钩子函数间的生命周期- 给
vue
实例对象添加el成员
,并且替换掉挂在的DOM元素。因为在之前console中打印的结果可以看到 **beforeMount
**之前el
上还是undefined
。
- 给
-
mounted
- 在
mounted
之前h1
中还是通过 ``进行占位的,因为此时还有挂在到页面上,还是JavaScript中的虚拟DOM形式存在的。在mounted
之后可以看到h1
中的内容发生了变化
- 在
-
beforeUpdate
钩子函数和updated
钩子函数间的生命周期- 当vue发现data中的数据发生了改变,会触发对应组件的重新渲染,先后调用beforeUpdate和updated钩子函数。我们在console中输入
vm.message = '触发组件更新'
- 当vue发现data中的数据发生了改变,会触发对应组件的重新渲染,先后调用beforeUpdate和updated钩子函数。我们在console中输入
-
beforeDestroy
和destroyed
钩子函数间的生命周期beforeDestroy
钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用。destroyed
钩子函数在Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁
5. Vue基础语法
5.1 Mustache语法
Mustache
语法即 Vue
中的 `` 语法。
5.2 v-once指令
- 该指令后不需要跟任何表达式
- 该指令表示元素和组件只渲染一次,不会随着数据的改变而改变。
<div id="app">
<h2></h2>
<h2 v-once></h2>
</div>
<script src="../js/vue.js"></script>
<script>
let app = new Vue({
el: "#app",
data: {
counter: 0,
message: ' Hello Vue,js',
firstName: 'kobe',
lastName: 'bryant',
},
methods: {
add: function () {
console.log("add被执行");
this.counter++;
},
sub: function () {
console.log("sub被执行");
this.counter--;
}
}
})
</script>
5.3 v-html指令
解析带有
html
标签的字符串v-html=""
5.4 v-text指令
v-text
指令的作用与 Mustache
相似,都是用于将数据显示在界面中。
接收的数据类型为 string
。
5.5 v-pr指令
不解析 `` 内部的语句
5.6 v-cloak指令
- 在
vue
解析之前,div
中存在属性v-clock
- 在
vue
解析之后,div
中没有属性v-clock
5.7 v-bind指令
作用:动态绑定属性
- 例如动态绑定
a
元素的href
属性;- 例如动态绑定
img
元素的src
属性;可以使用
:
代替v-bind
。
5.7.1 v-bind简单使用
5.7.2 v-bind动态绑定class
-
对象语法
-
数组语法
-
<div id="app"> <h2 class="title" :class="['active', 'line']"></h2> </div>
-
5.8.3 v-bind动态绑定style
对象语法
<div id="app">
<!-- <h2 :style="{属性名:属性值}"></h2>-->
<!--'0px'必须加上单引号 否则是当作变量去解析
驼峰标识的时候 fontSize 不需要加单引号
-标识的时候 'font-size'也需要加单引号
-->
<h2 :style="{fontSize: '50px'}"></h2>
<h2 :style="{'font-size': '50px'}"></h2>
<h2 :style="{fontSize: size}"></h2>
</div>
<script src="../js/vue.js"></script>
<script>
let app = new Vue({
el: "#app",
data: {
message: '你好呀',
size: '100px'
},
})
数组语法
6. 计算属性
6.1 计算属性的简单操作
computed
类似于methods
<div id="app">
<h2></h2>
<h2> </h2>
<h2></h2>
<!--计算属性-->
<h2></h2>
</div>
<script src="../js/vue.js"></script>
<script>
let app = new Vue({
el: "#app",
data: {
firstName: '刘',
lastName: '撷思',
},
/*计算属性*/
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName;
}
},
methods: {
getFullName: function () {
return this.firstName + ' ' + this.lastName;
}
}
})
</script>
6.2 计算属性的复杂操作
<div id="app">
<h2>总价格: </h2>
<h2>总价格: </h2>
</div>
<script src="../js/vue.js"></script>
<script>
let app = new Vue({
el: "#app",
data: {
books: [
{id: 110, name: '深入理解计算机原理', price: 100},
{id: 111, name: '现代操作系统', price: 105},
{id: 112, name: '深入理解Java虚拟机', price: 110},
]
},
/*计算属性*/
computed: {
totalPrice1: function () {
let ans = 0;
for (let i=0;i<this.books.length;i++) {
ans += this.books[i].price;
}
return ans;
},
totalPrice2: function () {
let ans = 0;
for (let book of this.books) {
ans += book.price;
}
return ans;
},
},
})
</script>
6.3 计算属性的 setter 和 getter
6.4 计算属性和methods的区别
- 计算属性会判断数据的值是否发生变化,如果没有发生变化,则不会重新执行,使用缓存的数据;
methods
调用几次执行几次,不会去判断数据的值是否发生了改变。
-
使用
methods
-
<div id="app"> <!--方法二:调用methods--> <h2></h2> <h2></h2> <h2></h2> <h2></h2> </div> <script src="../js/vue.js"></script> <script> let app = new Vue({ el: "#app", data: { firstName: '刘', lastName: '撷思', }, methods: { getFullName: function () { console.log('getFullName'); return this.firstName + ' ' + this.lastName; } } }) </script>
-
methods
调用了 4 次
-
-
使用计算属性
-
<div id="app"> <!-- 方法三:计算属性--> <h2></h2> <h2></h2> <h2></h2> <h2></h2> </div> <script src="../js/vue.js"></script> <script> let app = new Vue({ el: "#app", data: { firstName: '刘', lastName: '撷思', }, /*计算属性*/ computed: { fullName: function () { console.log('fullName'); return this.firstName + ' ' + this.lastName; } }, }) </script>
-
只有一次
-
7. v-on
7.1 v-on的基本使用和语法糖
在前端开发中,需要经常和用户交互。在这个时候,就需要监听用户发出的指令,如点击、拖拽、键盘时间等。
在
Vue
中监听事件使用v-on
指令。
v-on
介绍:
- 作用:绑定事件监听器
- 缩写:
@
7.2 v-on的参数传递
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
counter: 0,
},
methods: {
btn1Click() {
console.log("btn1Click");
}
}
})
</script>
-
如果该方法不需要额外参数,那么方法后的
()
可以不添加。-
<!--事件调用的方法没有参数--> <button @click="btn1Click()">按钮1</button> <button @click="btn1Click">按钮1-1</button>
-
-
如果方法本身中有一个参数,那么会默认将原生事件
event
参数传递进去-
<!--事件调用的方法需要参数--> <button @click="btn2Click(123)">按钮2</button> <button @click="btn2Click">按钮2-2</button>
-
-
如果需要同时传入某个参数,同时需要
event
时,可以通过$event
传入事件-
<!--事件调用的方法需要event对象,也需要其他参数--> <button @click="btn3Click">按钮3</button> <button @click="btn3Click()">按钮3-1</button> <button @click="btn3Click(123)">按钮3-2</button> <button @click="btn3Click(123, $event)">按钮3-4</button>
-
7.3 v-on修饰符
- 在某些情况下,我们拿到
event
的目的可能是进行一些事件处理。 Vue
提供了修饰符来帮助我们方便的处理一些事件:.stop
- 调用event.stopPropagation()
。.prevent
- 调用event.preventDefault()
。.{keyCode | keyAlias}
- 只当事件是从特定键触发时才触发回调。.native
- 监听组件根元素的原生事件。.once
- 只触发一次回调。
8. v-if 条件判断
8.1 v-if和v-else-if和v-else的使用
8.2 登录切换的小案例
切换账户登录跟邮箱登录
<div id="app">
<span v-if="type=='username'">
<label>用户账号:</label>
<input placeholder="用户账号">
</span>
<span v-else>
<label>邮箱地址:</label>
<input placeholder="邮箱地址">
</span>
<button @click="handle">切换类型</button>
</div>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
message: "你好呀",
score: 98,
type: 'username',
},
methods: {
handle() {
this.type = this.type == 'username' ? 'email' : 'username';
}
}
})
</script>
问题描述:
- 如果我们在有输入内容的情况下,切换了类型,我们会发现文字依然显示之前的输入的内容。
- 但是按道理讲,我们应该切换到另外一个
input
元素中了。 - 在另一个
input
元素中,我们并没有输入内容。 - 为什么会出现这个问题呢?
问题解答:
- 这是因为
Vue
在进行DOM
渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素。 - 在上面的案例中,
Vue
内部会发现原来的input
元素不再使用,直接作为else
中的input
来使用了。
解决方案:
- 如果我们不希望
Vue
出现类似重复利用的问题,可以给对应的input
添加key
- 并且我们需要保证
key
的不同
<div id="app">
<span v-if="type=='username'">
<label>用户账号:</label>
<input placeholder="用户账号" key="username-key">
</span>
<span v-else>
<label>邮箱地址:</label>
<input placeholder="邮箱地址" key="email-key">
</span>
<button @click="handle">切换类型</button>
</div>
8.3 v-show
8.3.1 v-show的使用
v-show
的用法和 v-if
非常相似,也用于决定一个元素是否渲染:
8.3.2 v-show与v-if的对比
v-if
和v-show
都可以决定一个元素是否渲染,那么开发中我们如何选择呢?v-if
当条件为false
时,压根不会有对应的元素在DOM
中。v-show
当条件为false
时,仅仅是将元素的display
属性设置为none
而已。
<div id="app">
<h2 v-if="isShow" id="1"></h2>
<h2 v-show="isShow" id="2"></h2>
<h2 v-if="!isShow" id="3">, !isShow</h2>
<h2 v-show="!isShow" id="4">, !isShow</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
message: "你好呀",
isShow: true,
},
})
</script>
开发中如何选择呢?
- 当需要在显示与隐藏之间切片很频繁时,使用
v-show
- 当只有一次切换时,通过使用
v-if
9. v-for 循环
9.1 v-for遍历数组
9.2 v-for遍历对象
9.3 v-for绑定和非绑定key
所以一句话,key的作用主要是为了高效的更新虚拟DOM。
9.4 数组中的哪些方式是响应式的
- 因为
Vue
是响应式的,所以当数据发生变化时,Vue
会自动检测数据变化,视图会发生对应的更新。 Vue
中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新push()
:从数组尾部添加元素pop()
:删除数组最后一个元素shift()
: 删除数组的第一个元素unshift()
: 从数组头部添加元素splice()
:用于添加或删除数组中的元素。splice(index,howmany,item1,.....,itemX)
sort()
reverse()
示例代码
<div id="app">
<ul>
<li v-for="item in letters" :key="item"></li>
<button @click="btnClick">按钮</button>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
letters: ['a', 'b', 'c', 'd'],
counter: 0,
},
methods: {
btnClick() {
}
}
})
</script>
-
push()
-
methods: { btnClick() { /*1 push方法*/ this.letters.push(this.counter++); } }
-
10. 购物车案例
10.1 搭建页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<div v-if="books.length">
<table>
<thead>
<tr>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(book, index) in books">
<!--<td v-for="value in book"></td>-->
<td></td>
<td></td>
<td></td>
<td>¥</td>
<td>
<button @click="decrement(index)" v-bind:disabled="book.count <= 1">-</button>
<button @click="increment(index)">+</button>
</td>
<td>
<button @click="removeClick(index)">移除</button>
</td>
</tr>
</tbody>
</table>
<h2>总价格:¥</h2>
</div>
<div v-else>
购物车为空
</div>
</div>
<script src="../js/vue.js"></script>
<script src="main.js"></script>
</body>
</html>
style.css
table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}
th, td {
padding: 8px 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
th {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
10.2 过滤器
const vue = new Vue({
el: '#app',
data: {
books: [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
},
],
},
/*过滤器*/
filters: {
showPrice(price) {
return price.toFixed(2);
}
}
})
10.3 改变商品数量+移除功能
const vue = new Vue({
el: '#app',
data: {
......
}
methods: {
increment(index) {
this.books[index].count++;
},
decrement(index) {
this.books[index].count--;
},
removeClick(index) {
this.books.splice(index, 1);
},
},
/*过滤器*/
filters: {
showPrice(price) {
return price.toFixed(2);
}
}
})
10.4 计算总价格
const vue = new Vue({
el: '#app',
data: {
books: [
......
]
},
computed: {
totalPrice() {
let ans = 0;
for (let i=0;i<this.books.length;i++) {
ans += this.books[i].price * this.books[i].count;
}
return ans.toFixed(2);
}
},
methods: {
......
},
/*过滤器*/
filters: {
......
}
})
11. v-model 表单绑定
11.1 JavaScript高阶函数的使用
-
filter
函数- ```js
const nums = [10, 20, 111, 222, 444, 40, 50]
/*filter中的回调函数必须返回boolean值
- 返回true时 函数内部会自动将这次回调的n加入到新的数组中
- 返回false时, 函数内部会过滤掉n*/ let newNums = nums.filter(function (n) { return n <= 100; }) console.log(newNums);
/[10,20,40,50]/ ```
- ```js
const nums = [10, 20, 111, 222, 444, 40, 50]
/*filter中的回调函数必须返回boolean值
-
map
函数-
const newNums = [10,20,40,50] let newNums2 = newNums.map(function (n) { return n * 2; }) /*[20,40,80,100]*/
-
-
reduce
函数-
/*3. reduce 对数组中所有内容进行汇总*/ newNums2.reduce(function (preValue, n) { return preValue + n; }, 0) // 第一次 preValue: 0 n: 20 // 第二次 preValue: return的返回值 n: 40
-
11.2 v-model的使用与原理
- 表单控件在实际开发中是非常常见的。特别是对于用户信息的提交,需要大量的表单。
Vue
中使用v-model
指令来实现表单元素和数据的双向绑定。- 案例场景
- 当我们在输入框输入内容时
- 因为
input
中的v-model
绑定了message
,所以会实时将输入的内容传递给message
,message
发生改变。 - 当
message
发生改变时,因为上面我们使用Mustache
语法,将message
的值插入到DOM
中,所以DOM
会发生响应的改变。 - 所以,通过
v-model
实现了双向的绑定。
11.3 v-model原理
v-model
其实是一个语法糖,它的背后本质上是包含两个操作:v-bind
绑定一个value
属性v-on
指令给当前元素绑定input
事件
进阶:
<div id="app">
<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
</div>
11.4 v-model结合radio类型
-
<div id="app"> <label for="gentle"> <input type="radio" id="gentle" name="gender" value="男" v-model="gender">男 </label> <label for="lady"> <input type="radio" id="lady" name="gender" value="女" v-model="gender">女 </label> <h2>您选择的性别是: </h2> </div> <script src="../js/vue.js"></script> <script> const vue = new Vue({ el: '#app', data: { message: '你好啊', gender: '', }, }) </script>
11.5 v-model结合checkbox
-
单选框
-
<!--单选框--> <label for="agree"> <input type="checkbox" id="agree" v-model="isAgree">同意协议 </label> <h2>您选择的是: </h2> <button :disabled="!isAgree">下一步</button> </div> <script src="../js/vue.js"></script> <script> const vue = new Vue({ el: '#app', data: { message: '你好啊', isAgree: false, }, }) </script>
-
-
复选框
-
<div> <!--多选框--> <input type="checkbox" value="篮球" v-model="hobbies">篮球 <input type="checkbox" value="足球" v-model="hobbies">足球 <input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球 <input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球 <h2>您选择是:</h2> </div> <script src="../js/vue.js"></script> <script> const vue = new Vue({ el: '#app', data: { message: '你好啊', isAgree: false, hobbies: [], }, }) </script>
-
11.6 v-model结合selcet
-
选择一个
-
<div id="app"> <!--1. 选择一个--> <select name="abc" v-model="fruit"> <option value="苹果">苹果</option> <option value="香蕉">香蕉</option> <option value="草莓">草莓</option> <option value="菠萝">菠萝</option> <option value="榴莲">榴莲</option> </select> <h2>您选择的水果是: </h2> </div> <script src="../js/vue.js"></script> <script> const vue = new Vue({ el: '#app', data: { message: '你好啊', fruit: '', }, }) </script>
-
-
多选
-
<div> <select name="abc" v-model="fruits" multiple> <option value="苹果">苹果</option> <option value="香蕉">香蕉</option> <option value="草莓">草莓</option> <option value="菠萝">菠萝</option> <option value="榴莲">榴莲</option> </select> <h2>您选择的水果是: </h2> <!--1. 选择多个--> </div> <script src="../js/vue.js"></script> <script> const vue = new Vue({ el: '#app', data: { message: '你好啊', fruit: '', fruits: [], }, }) </script>
-
11.7 v-model值绑定
动态的给
value
赋值而已
- 我们前面的
value
中的值,可以回头去看一下,都是在定义input
的时候直接给定的。 - 但是真实开发中,这些
input
的值可能是从网络获取或定义在data
中的。 - 所以我们可以通过
v-bind:value
动态的给value
绑定值。
<div id="app">
<label v-for="item in originHobbies">
<input type="checkbox" :value="item" v-model="fruits">
</label>
<h2>您选择的是: </h2>
</div>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
fruit: '',
fruits: [],
originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '悠悠球']
},
})
</script>
11.8 v-model修饰符的私用
-
lazy
修饰符:-
默认情况下,
v-model
默认是在input
事件中同步输入框的数据的。 -
也就是说,一旦有数据发生改变对应的
data
中的数据就会自动发生改变。 -
lazy
修饰符可以让数据在失去焦点或者回车时才会更新: -
<div id="app"> <!--1. lazy--> <input type="text" v-model.lazy="message"> <h2></h2> </div> <script src="../js/vue.js"></script> <script> const vue = new Vue({ el: '#app', data: { message: '你好啊', }, }) </script>
-
-
number
修饰符:-
默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。
-
但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
-
number
修饰符可以让在输入框中输入的内容自动转成数字类型: -
<div id="app"> <!--2. number--> <input type="number" v-model.number="age"> <h2> ---- </h2> </div> <script src="../js/vue.js"></script> <script> const vue = new Vue({ el: '#app', data: { message: '你好啊', age: 0, }, }) </script>
-
-
trim
修饰符:-
如果输入的内容首尾有很多空格,通常我们希望将其去除
-
trim
修饰符可以过滤内容左右两边的空格 -
<div id="app"> <!--3. 修饰符 trim--> <input type="text" v-model.trim="name"> <h2>您输入的name是:</h2> </div> <script src="../js/vue.js"></script> <script> const vue = new Vue({ el: '#app', data: { message: '你好啊', age: 0, name: '', }, }) </script>
-
12. 组件化开发
12.1 认识组件化
12.1.1 什么是组件化
- 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
- 但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
12.1.2 Vue.js组件思想
组件化是 Vue.js
中的重要思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
- 任何的应用都会被抽象成一颗组件树。
组件化思想的应用:
- 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 尽可能的将页面拆分成一个个小的、可复用的组件。
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
12.2 注册组件
12.2.1 注册组件的基本步骤
组件的使用分成三个步骤:
- 创建组件构造器
- 注册组件
- 使用组件。
12.2.2 注册组件步骤解析
Vue.extend()
:- 调用
Vue.extend()
创建的是一个组件构造器。 - 通常在创建组件构造器时,传入
template
代表我们自定义组件的模板。 - 该模板就是在使用到组件的地方,要显示的
HTML
代码。 - 事实上,这种写法在
Vue2.x
的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
- 调用
Vue.component()
:- 调用
Vue.component()
是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。 - 所以需要传递两个参数:1、注册组件的标签名 2、组件构造器。
- 调用
- 组件必须挂载在某个
Vue
实例下
12.3 其他组件补充
12.3.1 全局组件和局部组件
- 当我们通过调用
Vue.component()
注册组件时,组件的注册是全局的- 这意味着该组件可以在任意
Vue
示例下使用。
- 这意味着该组件可以在任意
- 如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件
12.3.2 父组件和子组件的区分
<div id="app">
<!-- <cpn1></cpn1>-->
<cpn2></cpn2>
<p>------</p>
<cpn1></cpn1>
</div>
<script src="../js/vue.js"></script>
<script>
<!--1. 第一个组件 子组件-->
const cpnC1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容 哈哈哈哈</p>
</div>
`
})
/*2. 第二个组件 父组件*/
const cpnC2 = Vue.extend({
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容 嘿嘿嘿嘿</p>
<cpn1></cpn1>
</div>
`,
components: {
cpn1: cpnC1
}
})
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
},
components: {
// cpn1: cpnC1,
cpn2: cpnC2,
}
})
</script>
12.3.3 注册组件的语法糖写法
-
全局组件的语法糖
-
<script> /*1 创建组件构造器对象*/ const cpnC = Vue.extend({ template: `<div> <h2>我是标题</h2> <p>我是内容,哈哈哈哈</p> <p>我是内容,呵呵呵呵</p> </div>` }) /*2. 注册组件*/ Vue.component('my-cpn', cpnC) /*上面两步等价于下面*/ Vue.component('mu-cpn', { template: `<div> <h2>我是标题</h2> <p>我是内容,哈哈哈哈</p> <p>我是内容,呵呵呵呵</p> </div>` }) </script>
-
-
局部组件的语法糖
-
<script> /*注册局部组件的语法糖*/ const vue = new Vue({ el: '#app', data: { message: '你好啊', }, component: { 'cpn2': { template: `<div> <h2>我是标题</h2> <p>我是内容,哈哈哈哈</p> <p>我是内容,呵呵呵呵</p> </div>` } }) </script>
-
12.3.4 组件模板分离
-
方式一:
-
<!--1 方式一:script标签 类型必须为text/x-template--> <script type="text/x-template" id="cpn"> <div> <h2>我是标题</h2> <p>我是内容</p> </div> </script> <script> /*1 注册全局组件*/ Vue.component('my-cpn', { template: `#cpn` }) </script>
-
-
方式二:
-
<!--方式二:template--> <template id="cpn"> <div> <h2>我是标题2</h2> <p>我是内容2</p> </div> </template> <script src="../js/vue.js"></script> <script> /*1 注册全局组件*/ Vue.component('my-cpn', { template: `#cpn` }) </script>
-
<div id="app">
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<!--1 方式一:script标签 类型必须为text/x-template-->
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
</script>
<!--方式二:template-->
<template id="cpn">
<div>
<h2>我是标题2</h2>
<p>我是内容2</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
/*1 注册全局组件*/
Vue.component('my-cpn', {
template: `#cpn`
})
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
}
})
</script>
12.4 组件不能访问Vue实例数据
- 组件是一个单独功能模块的封装:
- 这个模块有属于自己的
HTML
模板,也应该有属性自己的数据data
。
- 这个模块有属于自己的
组件是不能直接访问
Vue
实例中的数据的
12.4.1 组件的 data
属性
Vue
组件应该有自己保存数据的地方
- 组件对象也有一个
data
属性(也可以有methods
等属性,下面我们有用到) - 只是这个
data
属性必须是一个函数 - 而且这个函数返回一个对象,对象内部保存着数据
12.4.2 组件不能访问Vue示例数据的原因
在于
Vue
让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响
13. 父子组件通信
子组件是不能引用父组件或者Vue实例的数据的。
但是,在开发中,往往一些数据确实需要从上层传递到下层:
- 比如在一个页面中,我们从服务器请求到了很多的数据。
- 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
- 这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
如何进行父子组件间的通信呢
- 通过
props
向子组件传递数据 - 通过事件向父组件发送消息
13.1 父传子 props
13.1.1 使用方法
在组件中,使用选项 props
来声明需要从父级接收到的数据。
props
的值有两种方式:
- 方式一:字符串数组,数组中的字符串就是传递时的名称。
- 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。推荐
方式一:
<div id="app">
<cpn :cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
<lu>
<li v-for="item in cmovies"></li>
</lu>
<h2></h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['进击的巨人', '海贼王', '火影忍者', '鬼灭之刃'],
},
components: {
'cpn': {
template: '#cpn',
props: ['cmovies', 'cmessage'],
}
}
})
</script>
传对象
<div id="app">
<cpn :cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
<lu>
<li v-for="item in cmovies"></li>
</lu>
<h2></h2>
</div>
</template>
<script>
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['进击的巨人', '海贼王', '火影忍者', '鬼灭之刃'],
},
components: {
'cpn': {
template: '#cpn',
// props: ['cmovies', 'cmessage'],
props: {
cmessage: {
type: String, /*类型限制*/
default: '', /*默认值*/
required: true, /*true表示使用cmessage这个属性的时候必须要传值*/
},
cmovies: {
type: Array,
/*类型是数组或者对象的时候,默认值必须要是一个函数*/
default() {
return [];
}
}
}
}
}
})
</script>
支持的数据类型:
String
Number
Boolean
Array
Object
Date
Function
Symbol
13.1.2 props驼峰标识
不支持驼峰标识
<div id="app">
<cpn :cInfo="info"></cpn>
</div>
<template id = "cpn">
<h2></h2>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
props: {
cInfo: {
type: Object,
default() {
return {};
}
}
}
}
const vue = new Vue({
el: '#app',
data: {
info: {
name: '刘云杰',
age: 18,
height: 188,
}
},
components: {
cpn
}
})
</script>
解决方法
cInfo
改为c-info
13.2 子传父
props
用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。- 我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
- 什么时候需要自定义事件呢?
- 当子组件需要向父组件传递数据时,就要用到自定义事件了。
- 我们之前学习的
v-on
不仅仅可以用于监听 DOM 事件,也可以用于组件间的自定义事件。
- 自定义事件的流程:
- 在子组件中,通过
$emit()
来触发事件。 - 在父组件中,通过
v-on
来监听子组件事件。
- 在子组件中,通过
<!--父组件模板-->
<div id="app">
<!--这里不支持驼峰标识-->
<vpn @item-click="apcClick"></vpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button v-for="item in categories"
@click="btnClick(item)">
</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
/*1. 子组件*/
const vpn = {
template: '#cpn',
data() {
return {
categories: [
{id: 'aaa', name: '热门推荐'},
{id: 'bbb', name: '手机数码'},
{id: 'ccc', name: '家用家电'},
{id: 'ddd', name: '电脑办公'},
]
}
},
methods: {
btnClick(item) {
/*向父组件通信 自定义事件
* item-click是事件的名称
* item是事件要传递的参数
*/
this.$emit('item-click', item)
}
}
}
/*2. 父组件*/
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
},
components: {
vpn
},
methods: {
apcClick(item) {
console.log(item);
}
}
})
</script>
13.3 父子间通信案例1
- 父组件通过
props
将counter
传递给子组件;- 子组件通过自定义事件
add-counter
和decrement-counter
将点击事件传递给父组件。
<!--父组件模板-->
<div id="app">
<!--这里不支持驼峰标识-->
<vpn @add-counter="addCounter" @decrement-counter="decrementCounter" :child-counter="counter"></vpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<h2></h2>
<button @click="increment()">+</button>
<button @click="decrement()">-</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
/*1. 子组件*/
const vpn = {
template: '#cpn',
props: {
childCounter: {
required: true,
}
},
methods: {
increment() {
this.$emit('add-counter');
},
decrement() {
this.$emit('decrement-counter')
}
}
}
/*2. 父组件*/
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
counter: 0,
},
components: {
vpn
},
methods: {
addCounter() {
this.counter++;
},
decrementCounter() {
this.counter--;
}
}
})
</script>
13.4 父子间通信案例2
注意:
- 子组件中
number1
和number2
的值初始来源于num1
和num2
,将number1
和number2
与<input>
双向绑定的时候不能直接绑定number1
和number2
,需要找一个中间值。
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props: </h2>
<h2>data: </h2>
<input type="text" :value="dnumber1" @input="num1Input">
<h2>props: </h2>
<h2>data: </h2>
<input type="text" :value="dnumber2" @input="num2Input">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0,
},
methods: {
num1change(value) {
this.num1 = parseFloat(value);
},
num2change(value) {
this.num2 = parseFloat(value);
},
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number,
},
data() {
return {
dnumber1: this.number1,
dnumber2: this.number2,
}
},
methods: {
num1Input(event) {
this.dnumber1 = event.target.value;
this.$emit("num1change", this.dnumber1);
this.dnumber2 = this.dnumber1 * 100;
this.$emit("num2change", this.dnumber2);
},
num2Input(event) {
this.dnumber2 = event.target.value;
this.$emit("num2change", this.dnumber2);
this.dnumber1 = this.dnumber2 / 100;
this.$emit("num1change", this.dnumber1);
},
}
},
}
})
</script>
13.5 父子组件的访问方式
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件。
- 父组件访问子组件:使用
$children
或$refs
- 子组件访问父组件:使用
$parent
13.5.1 children
方式(不推荐)
-
<div id="app"> <cpn></cpn> <button @click="btnClick">按钮</button> </div> <template id="cpn"> <div>我是子组件</div> </template> <script src="../js/vue.js"></script> <script> const vue = new Vue({ el: '#app', data: { message: '你好啊', }, methods: { btnClick() { console.log(this.$children); this.$children[0].showMessage(); } }, components: { cpn: { template: '#cpn', methods: { showMessage() { console.log('showMessage'); } } }, } }) </script>
13.5.2 refs
方式
14. 插槽slot
组件的插槽
- 组件的插槽也是为了让我们封装的组件更加具有扩展性。
- 让使用者可以决定组件内部的一些内容到底展示什么
14.1 插槽的基本使用
- 插槽的基本使用
<slot></slot>
- 插槽的默认值
<slot>xxxxx</slot>
- 如果有多个值,同时放入到组件中进行替换时,一起作为替换元素
<div id="app">
<cpn><button>按钮</button></cpn>
<cpn><span>哈哈</span></cpn>
<cpn><i>呵呵</i></cpn>
<cpn><button>按钮2</button></cpn>
<cpn>
<i>hahahaha</i>
<b>yiyiyiyi</b>
<div>divdiv</div>
<p>ppppppppp</p>
</cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是插槽</h2>
<p>我是组件</p>
<slot><button>button</button></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
},
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
14.2 具名插槽
给
slot
元素一个name
属性即可
<slot name='myslot'></slot>
<div id="app">
<cpn>
<span slot="center">标题</span>
</cpn>
<cpn>
<button slot="left">按钮</button>
</cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
<slot><span>哈哈哈</span></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
},
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
14.3 编译作用域
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
<div id="app">
<cpn v-slot="isShow"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是内容 哈哈哈</p>
<button v-show="isShow">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
isShow: true,
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false,
}
}
}
},
})
</script>
14.4 作用域插槽的使用
目的:父组件替换插槽的标签,但是内容由子组件来提供。
我们先提一个需求:
- 子组件中包括一组数据,比如:
Languages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']
- 需要在多个界面进行展示:
- 某些界面是以水平方向一一展示的,
- 某些界面是以列表形式展示的,
- 某些界面直接展示一个数组
- 内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
- 利用
slot
作用域插槽就可以了
- 利用
<div id="app">
<cpn></cpn>
<cpn>
<!--目的是获取子组件中的pLanguages-->
<template slot-scope="slot">
<span v-for="item in slot.data">-</span>
</template>
</cpn>
<cpn>
<!--目的是获取子组件中的pLanguages-->
<template slot-scope="slot">
<span></span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages"></li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
message: '你好啊',
},
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++'],
}
}
},
},
})
</script>
15. webpack
15.1 什么是webpack
webpack是一个现代的JavaScript应用的静态 模块打包 工具。
从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。
cnpm install webpack@3.6.0 -g
webpack --version
15.2 webpack基本使用
webpack 3.6.0
的打包命令:
webpack ./src/main.js ./dist/bundle.js
// 1.使用commonjs的模块化规范
const {add, mul} = require('./mathUtil.js')
console.log(add(20, 30));
console.log(mul(20, 30));
// 2. 使用ES6的模块化规范
import {
name, age, height
} from "./info";
console.log(name);
console.log(age);
console.log(height)
15.3 webpack.config.js 和 package.json 配置
如果每次使用 webpack
的命令都需要写上入口和出口作为参数,就非常麻烦,有没有一种方法可以将这两个参数写到配置中,在运行时,直接读。
-
npm init
- 会创建
package.json
文件
- 会创建
-
新建
webpack.config.js
文件-
const path = require('path') module.exports = { entry: './src/main.js',/*入口*/ output: { path: path.resolve(__dirname, 'dist'),/*路径 这里只能填绝对路径*/ filename: 'bundle.js', /*文件名*/ },/*出口*/ }
-
-
进阶:使用
npm run build
命令代替webpack
-
配置
package.json
-
{ "name": "meetwebpack", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" // 添加命令 默认先运行本地的webpack 而非全局webpack }, "author": "", "license": "ISC" }
15.4 webpack中使用css文件的配置
webpack
主要是用来处理编写的js
代码- 但是开发中不仅仅需要处理
js
文件,还需要加载css
、图片等。这个时候就需要给webpack
扩展对应的loader
即可。
- 但是开发中不仅仅需要处理
loader
使用过程- 通过
npm
安装需要使用的loader
- 在
webpack.config.js
中的mudule
关键字下进行配置
- 通过
15.4.1 css-loader
-
新建一个
css
文件normal.css
-
body { background-color: antiquewhite; }
-
-
main.js
中引入该css
文件-
// 3. 依赖css文件 require('./css/normal.css')
-
-
安装
css-loader
和style-loader
-
npm install --save-dev style-loader npm install --save-dev css-loader
-
-
配置
webpack.config.js
-
const path = require('path') module.exports = { /*处理Js文件*/ entry: './src/main.js',/*入口*/ output: { path: path.resolve(__dirname, 'dist'),/*路径 这里只能填绝对路径*/ filename: 'bundle.js', /*文件名*/ },/*出口*/ /*处理CSS文件*/ module: { rules: [ { test: /\.css$/i, /*css-loader只负责加载css文件 style-loader负责将样式添加到DOM中 使用多个loader的时候 读取顺序:从右向左 * */ use: ["style-loader", "css-loader"], }, ], }, }
-
css-loader
只负责加载css
文件style-loader
负责将样式添加到DOM
中- 使用多个
loader
的时候 读取顺序:从右向左
15.4.2 less文件使用
-
新建
special.less
文件-
@fontSize: 50px; @fontColor: orange; body { font-size: @fontSize; color: @fontColor; }
-
-
main.js
中引入该less
文件-
// 4. 依赖less文件 require('./css/special.less')
-
-
安装
less-loader
-
npm install less less-loader --save-dev # 注意对应版本号
-
-
配置
webpack.config.js
-
const path = require('path') module.exports = { /*处理Js文件*/ entry: './src/main.js',/*入口*/ output: { path: path.resolve(__dirname, 'dist'),/*路径 这里只能填绝对路径*/ filename: 'bundle.js', /*文件名*/ },/*出口*/ /*处理CSS文件*/ module: { rules: [ { test: /\.css$/i, /*css-loader只负责加载css文件 style-loader负责将样式添加到DOM中 使用多个loader的时候 读取顺序:从右向左 * */ use: ["style-loader", "css-loader"], }, { test: /\.less$/i, use: [ { loader: 'style-loader', }, { loader: 'css-loader', }, { loader: 'less-loader', }, ], }, ], }, }
-
15.4.3 webpack图片文件的处理
-
添加 图片 文件
-
修改
css
文件-
body { background: url("../img/avatar.jpg"); }
-
-
安装
file-loader
-
npm install file-loader --save-dev
-
-
配置
webpack.config.js
-
const path = require('path') module.exports = { /*处理Js文件*/ entry: './src/main.js',/*入口*/ output: { path: path.resolve(__dirname, 'dist'),/*路径 这里只能填绝对路径*/ filename: 'bundle.js', /*文件名*/ publicPath: 'dist/', /*配置上这个以后涉及到url的操作会自动把这个路径添加进去*/ },/*出口*/ /*处理CSS文件*/ module: { rules: [ { test: /\.css$/i, /*css-loader只负责加载css文件 style-loader负责将样式添加到DOM中 使用多个loader的时候 读取顺序:从右向左 * */ use: ["style-loader", "css-loader"], }, { test: /\.less$/i, use: [ { loader: 'style-loader', }, { loader: 'css-loader', }, { loader: 'less-loader', }, ], }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]' /*文件的路径已经名字*/ }, // mode: 'development', }, ], }, ], }, }
-
15.4.4 ES6语法处理
如果希望将ES6的语法转成ES5,那么就需要使用 babel
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
配置 webpack.config.js
15.4.5 wepack配置vue
-
cnpm install vue@2.5.21 --save '@2.5.21' 指的是vue的版本号
-
main.js
-
// 5. 使用Vue进行开发 import Vue from 'vue' const app = new Vue({ el: '#app', data: { message: 'Hello webpack', } })
-
-
出现问题
-
解决方法
-
webpack.config.js
-
const path = require('path') module.exports = { /*处理Js文件*/ entry: './src/main.js',/*入口*/ output: { path: path.resolve(__dirname, 'dist'),/*路径 这里只能填绝对路径*/ filename: 'bundle.js', /*文件名*/ publicPath: 'dist/', /*配置上这个以后涉及到url的操作会自动把这个路径添加进去*/ },/*出口*/ /*处理CSS文件*/ module: { rules: [ { test: /\.css$/i, /*css-loader只负责加载css文件 style-loader负责将样式添加到DOM中 使用多个loader的时候 读取顺序:从右向左 * */ use: ["style-loader", "css-loader"], }, { test: /\.less$/i, use: [ { loader: 'style-loader', }, { loader: 'css-loader', }, { loader: 'less-loader', }, ], }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]' /*文件的路径已经名字*/ }, // mode: 'development', }, ], }, ], }, /*指明vue使用runtime-compile版本*/ resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js', } } }
-
15.4.6 el与template的区别
- 在前面的
Vue
实例中,我们定义了el
属性,用于和index.html
中的#app
进行绑定,让Vue
实例之后可以管理它其中的内容 - 这里,我们可以将
div
元素中的 `` 内容删掉,只保留一个基本的id
为div
的元素 - 但是如果我依然希望在其中显示 `` 的内容,应该怎么处理呢?
- 我们可以再定义一个
template
属性,代码如下:
// 5. 使用Vue进行开发
import Vue from 'vue'
const app = new Vue({
el: '#app',
template:
`<div>
<h2></h2>
<button @click="btnClick">按钮</button>
<h2></h2>
</div>`,
data: {
message: 'Hello webpack',
name: '刘云杰',
methods : {
btnClick() {
console.log('lyj ');
}
}
}
})
15.4.7 vue的终极使用方式
-
优化第一步:
-
// 5. 使用Vue进行开发 import Vue from 'vue' const App = { template: `<div> <h2></h2> <button @click="btnClick">按钮</button> <h2></h2> </div>`, data() { return { message: 'Hello webpack', name: '刘云杰', } }, methods: { btnClick() { } } } const app = new Vue({ el: '#app', template: `<App/>`, data: { }, components: { App } })
-
-
优化第二步:
-
新建
app.js
-
export default { template: `<div> <h2></h2> <button @click="btnClick">按钮</button> <h2></h2> </div>`, data() { return { message: 'Hello webpack', name: '刘云杰', } }, methods: { btnClick() { } } }
-
-
修改
main.js
-
// 5. 使用Vue进行开发 import Vue from 'vue' import App from './vue/app' const app = new Vue({ el: '#app', template: `<App/>`, data: { }, components: { App } })
-
-
-
优化第三步:
-
新建
App.vue
文件-
<template> <div> <h2 class="title"></h2> <button @click="btnClick">按钮</button> <h2></h2> </div> </template> <script> # import cpn from './cpn' #components: { # cpn #} export default { name: "App", data() { return { message: 'Hello webpack', name: '刘云杰', } }, methods: { btnClick() { } } } </script> <style scoped> .title { color: aquamarine; } </style>
-
-
15.4.8 webpack的plugin的使用
什么是
plugin
plugin
是插件的意思,通常是用于对某个现有的架构进行扩展。webpack
中的插件,就是对webpack
现有功能的各种扩展,比如打包优化,文件压缩等等。
loader
和plugin
区别
loader
主要用于转换某些类型的模块,它是一个转换器。plugin
是插件,它是对webpack
本身的扩展,是一个扩展器。
plugin
的使用过程:
- 步骤一:通过
npm
安装需要使用的plugins
(某些webpack已经内置的插件不需要安装) - 步骤二:在
webpack.config.js
中的plugins中配置插件。
-
添加版权的
plugin
-
插件名字叫
BannerPlugin
,属于webpack
自带的插件。 -
修改
webpack.config.js
文件 -
const webpack = require('webpack') module.exports = { /*......*/ /*插件*/ plugins : [ new webpack.BannerPlugin('版权问题'), ] }
-
-
HtmlWebpackPlugin
-
将
index.html
文件打包到dist
文件夹中 -
HtmlWebpackPlugin
插件可以为我们做这些事情:- 自动生成一个
index.html
文件(可以指定模板来生成) - 将打包的
js
文件,自动通过script
标签插入到body
中
- 自动生成一个
-
安装命令
npm install html-webpack-plugin --save-dev
-
配置
webpack.config.js
文件-
const path = require('path') const webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { /*插件*/ plugins : [ new webpack.BannerPlugin('版权问题'), new HtmlWebpackPlugin({ template: 'index.html', }), ] }
-
-
-
uglifyjs-webpack-plugin
- 对
js
等文件进行压缩处理 npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
- 修改
webpack.config.js
文件
- 对
-
webpack-dev-server
- 作用:搭建本地开发服务器
npm install --save-dev webpack-dev-server@2.9.1
contentBase
:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./distport
:端口号inline
:页面是否实时刷新historyApiFallback
:在SPA页面中,依赖HTML5的history模式
webpack.config.js
文件配置--open
参数表示直接打开浏览器
16. vue-cli 脚手架
16.1 vue-cli介绍
CLI是什么意思?
CLI
是Command-Line Interface
, 翻译为命令行界面, 但是俗称脚手架.Vue CLI
是一个官方发布vue.js
项目脚手架- 使用
vue-cli
可以快速搭建Vue
开发环境以及对应的webpack
配置
使用vue cli的前提
Node.js
webpack
安装vue脚手架
npm install -g @vue/cli
- 上面安装的是Vue CLI3的版本,如果需要想按照Vue CLI2的方式初始化项目时不可以的。
cnpm install @vue/cli-init -g
- Vue CLI2初始化项目
vue init webpack my-project
- Vue CLI3初始化项目
vue create my-project
16.2 vue-cli2
- 安装过程:
- 目录结构详情
16.3 Runtime-Compiler和Runtime-only的区别
- 简单总结
- 如果在之后的开发中,你依然使用
template
,就需要选择Runtime-Compiler
; - 如果你之后的开发中,使用的是
.vue
文件夹开发,那么可以选择Runtime-only
。
- 如果在之后的开发中,你依然使用
- 项目对比
- Vue程序运行过程
16.3.1 render函数
-
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', /*render函数*/ render: function (createElement) { // 1. createElement('标签', {标签的属性}, ['标签的内容']) return createElement('h2', {class: 'box'}, ['Hello World', createElement('button', ['按钮'])]); } // components: { App }, // template: '<App/>' })
-
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' Vue.config.productionTip = false const cpn = { template: `<div></div>`, data() { return { message : '我是组件', } } } /* eslint-disable no-new */ new Vue({ el: '#app', /*render函数*/ render: function (createElement) { // 2. 传入组件 return createElement(cpn); } // components: { App }, // template: '<App/>' })
-
进阶
-
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', /*render函数*/ render: function (createElement) { // 2. 传入组件 return createElement(App); } // components: { App }, // template: '<App/>' })
16.3 vue-cli3
16.3.1 基础介绍
- vue-cli 3 与 2 版本有很大区别
- vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3
- vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录
- vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
- 移除了static文件夹,新增了public文件夹,并且index.html移动到public中
配置相关
16.3.2 图像化界面
vue ui
16.3.2 自定义配置
- 创建
vue.config.js
- 添加自定义的配置
16.3.3 箭头函数和 this指向
const 函数名 = (参数列表)=>{}
-
例子1:
-
<script> /*箭头函数:定义函数的一种方式*/ // 1. function定义函数 const aaa = function () { } // 2. 对象自变量中定义函数 const obj = { bbb() { } } // 3. ES6中的箭头函数 // const 函数名 = (参数列表) => {} const ccc = () => { /*箭头函数最基础的使用 * * 1. 没有参数 * 2. 没有返回值 * */ } </script>
-
-
例子2:只有一个参数
-
<script> // 1. 参数问题 // 1.1 放入两个参数 const sum = (num1, num2) => { return num1 + num2; } // 1.2 放入一个参数的时候 ()可以省略 const power = num => { return num * num; } </scripts>
-
-
例子3:只有一行代码
-
<script> // 2.返回值问题 // 2.1 函数代码块中有多行代码时 正常写 const test = () => { /*1. 打印Hello World*/ console.log('Hello World'); /*2. 打印Hello Vuejs*/ console.log('Hello Vue.js'); } // 2.2 函数代码块中只有一行代码 /* * const mul = (num1, num2) => { return num1 * num2; } * 等价于下面 * */ const mul = (num1, num2) => num1 * num2; const demo = () => console.log('hello world'); </script>
-
this
指向
-
<script> /*什么时候使用*/ const onj = { aaa() { setTimeout(function () { console.log(this); // windows }) /* 箭头函数的this引用的是最近作用域中的this * * 箭头函数中的this是如何查找的?向外层作用域一层层查找this,直到有this的定义。 * * */ setTimeout(() => { console.log(this); // Obj对象 }) } } onj.aaa(); </script>
16.4 路由与映射
16.4.1 认识路由
路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动. — 维基百科
- 路由器提供了两种机制: 路由和转送.
- 路由是决定数据包从来源到目的地的路径.
- 转送将输入端的数据转移到合适的输出端.
- 路由中有一个非常重要的概念叫路由表.
- 路由表本质上就是一个映射表, 决定了数据包的指向.
前端渲染与后端渲染
- 后端路由是指后端处理 URL 和 页面之间的映射关系。
- 早期的网站开发整个HTML页面是由服务器来渲染
- 一个页面有自己对应的网址, 也就是URL.
- URL会发送到服务器, 服务器会通过正则对该URL进行匹配, 并且最后交给一个Controller进行处理.
- Controller进行各种处理, 最终生成HTML或者数据, 返回给前端.
- 这就完成了一个IO操作.
- 后端路由的缺点:
- 一种情况是整个页面的模块由后端人员来编写和维护的.
- 另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码.
- 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情
- 早期的网站开发整个HTML页面是由服务器来渲染
- 前后端分离
- 后端只提供API来返回数据, 前端通过获取数据, 并且可以通过JavaScript将数据渲染到页面中
- 前端渲染:浏览器中网页内的大部分内容,都是由前端编写的 JS 代码在浏览器中执行,最终渲染出来的网页。
- 单页面富应用阶段
- 在前后端分离的基础上加了一层前端路由,即维护前端来维护一套路由规则。
- 整个网页中只有一个 html 页面。
url
的hash
URL
的hash
也就是锚点(#), 本质上是改变window.location
的href
属性;- 可以通过直接赋值
location.hash
来改变href
, 但是页面不发生刷新。
16.4.2 vue-router基础
vue-router是基于路由和组件的
- 路由用于设定访问路径, 将路径和组件映射起来.
- 在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.
- 步骤一: 安装
vue-router
npm install vue-router --save
- 步骤二: 在模块化工程中使用它(因为是一个插件, 所以可以通过
Vue.use()
来安装路由功能)- 第一步:导入路由对象,并且调用
Vue.use(VueRouter)
- 第二步:创建路由实例,并且传入路由映射配置
- 第三步:在
Vue
实例中挂载创建的路由实例
- 第一步:导入路由对象,并且调用
/*配置路由的相关信息*/
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
// 1.通过Vue.use(插件) 安装插件
Vue.use(Router)
// 2. new Router({}) 创建 VueRouter对象 3. export 导出路由
export default new Router({
// 配置路由和组件之间的应用关系
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
})
16.4.3 路由映射配置
第一步: 创建路由组件
第二步: 配置路由映射: 组件和路径映射关系
第三步: 使用路由: 通过 <router-link>
和 <router-view>
<router-link>
: 该标签是一个vue-router
中已经内置的组件, 它会被渲染成一个<a>
标签.
<router-view>
: 该标签会根据当前的路径, 动态渲染出不同的组件.网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和
<router-view>
处于同一个等级.在路由切换时, 切换的是
<router-view>
挂载的组件, 其他内容不会发生改变.
举例
-
新建
HelloWorld.vue
和about.vue
-
<template> <div> <h2>我是about.js</h2> <p>我是about.js的内容, 哈哈哈</p> </div> </template> <script> export default { name: "about" } </script> <style scoped> </style>
-
-
index.js
中配置映射-
/*配置路由的相关信息*/ import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import about from "../components/about"; // 1.通过Vue.use(插件) 安装插件 Vue.use(Router) // 2. new Router({}) 创建 VueRouter对象 3. export 导出路由 export default new Router({ // 配置路由和组件之间的应用关系 routes: [ /*一个映射关系一个对象*/ { path: '/', name: 'HelloWorld', component: HelloWorld }, { path: '/about', name: 'about', component: about, } ] })
-
-
App.vue
中使用路由-
<template> <div id="app"> <router-link to="/home">HelloWorld</router-link> <router-link to="/about">about</router-link> <!-- <router-view> 作用是占位 页面要渲染在哪个位置 --> <router-view></router-view> </div> </template> <script> export default { name: 'App' } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
-
16.4.4 路由的默认值和修改为history模式
路由的默认值:
path
配置的是根路径:/
redirect
是重定向, 也就是我们将根路径重定向到/
的路径下, 这样就可以得到我们想要的结果了.
在 router/index.js
添加一条映射
/*配置路由的相关信息*/
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import about from "../components/about";
// 1.通过Vue.use(插件) 安装插件
Vue.use(Router)
// 2. new Router({}) 创建 VueRouter对象 3. export 导出路由
export default new Router({
// 配置路由和组件之间的应用关系
routes: [
/*一个映射关系一个对象*/
{
path: '',
redirect: '/',
},
{
path: '/home',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/about',
name: 'about',
component: about,
}
]
})
将 hash 模式修改为 history 模式
添加
mode: 'history'
/*配置路由的相关信息*/
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import about from "../components/about";
// 1.通过Vue.use(插件) 安装插件
Vue.use(Router)
// 2. new Router({}) 创建 VueRouter对象 3. export 导出路由
export default new Router({
// 配置路由和组件之间的应用关系
routes: [
/*一个映射关系一个对象*/
{
path: '',
redirect: '/home',
},
{
path: '/home',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/about',
name: 'about',
component: about,
}
],
mode: 'history',
})
16.4.5 router-link的其他属性
- 在前面的
<router-link>
中, 我们只是使用了一个属性:to
, 用于指定跳转的路径. <router-link>
还有一些其他属性:tag
:tag
可以指定<router-link>
之后渲染成什么组件, 比如<router-link to='/home' tag='li'>
会被渲染成一个<li>
元素, 而不是<a>
。replace
:replace
不会留下history
记录, 所以指定replace
的情况下,后退键返回不能返回到上一个页面中。active-class
: 当<router-link>
对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active
的class
。- 设置
active-class
可以修改默认的名称.在进行高亮显示的导航菜单或者底部tabbar
时, 会使用到该类; - 但是通常不会修改类的属性, 会直接使用默认的
router-link-active
即可。
- 设置
16.4.6 vue-router动态路由
在某些情况下,一个页面的 path
路径可能是不确定的,比如我们进入用户界面时,希望是如下的路径:
/user/aaaa
或/user/bbbb
- 除了有前面的
/user
之外,后面还跟上了用户的ID
- 这种
path
和Component
的匹配关系,我们称之为动态路由(也是路由传递数据的一种方式)。
例子
-
新建
User.vue
文件-
<template> <div> <h2>我是用户界面</h2> <p>我是用户的相关信息,嘿嘿嘿</p> <h2></h2> </div> </template> <script> export default { name: "User", computed: { userId() { return this.$route.params.userId; // 注意:这里的userId对应的是App.vue中的userId } } } </script> <style scoped> </style>
-
-
index.js
配置路由-
/*配置路由的相关信息*/ import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import about from "../components/about"; import User from "../components/User"; // 1.通过Vue.use(插件) 安装插件 Vue.use(Router) // 2. new Router({}) 创建 VueRouter对象 3. export 导出路由 export default new Router({ // 配置路由和组件之间的应用关系 routes: [ { path: '/user/:userId', name: 'user', component: User, } ], mode: 'history' })
-
-
App.vue
传值-
<template> <div id="app"> <router-link to="/">HelloWorld</router-link> <router-link to="/about">about</router-link> <router-link :to="'/user/'+userId">user</router-link> <!-- <router-view> 作用是占位 页面要渲染在哪个位置 --> <router-view></router-view> </div> </template> <script> export default { name: 'App', data() { return { userId: 'lyj', } } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
-
16.4.7 路由的懒加载
- 当打包构建应用时,Javascript 包会变得非常大,影响页面加载。
- 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
概念
- 首先, 我们知道路由中通常会定义很多不同的页面.
- 这个页面最后被打包在哪里呢? 一般情况下, 是放在一个js文件中.
- 但是, 页面这么多放在一个js文件中, 必然会造成这个页面非常的大.
- 如果我们一次性从服务器请求下来这个页面, 可能需要花费一定的时间, 甚至用户的电脑上还出现了短暂空白的情况.
- 如何避免这种情况呢? 使用路由懒加载就可以了。
路由懒加载做了什么?
- 路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块.
- 只有在这个路由被访问到的时候, 才加载对应的组件
懒加载的使用
- 方式一:结合Vue的异步组件和Webpack的代码分析;
const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
- 方式二:AMD写法;
const About = resolve => require(['../components/About.vue'], resolve);
- 方式三:在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割。(推荐)
const Home = () => import('../components/Home.vue')
16.4.8 路由的嵌套
- 嵌套路由是一个很常见的功能
- 比如在
home
页面中, 我们希望通过/home/news
和/home/message
访问一些内容. - 一个路径映射一个组件, 访问这两个路径也会分别渲染两个组件.
- 比如在
- 实现嵌套路由有两个步骤:
- 创建对应的子组件, 并且在路由映射中配置对应的子路由.
- 在组件内部使用
<router-view>
标签.
例子:
-
新建两个子组件
HomeNews.vue
和HomeMessage.vue
-
<template> <div> <ul> <li>新闻1</li> <li>新闻2</li> <li>新闻3</li> <li>新闻4</li> </ul> </div> </template> <script> export default { name: "HomeNews" } </script> <style scoped> </style>
-
<template> <div> <ul> <li>消息1</li> <li>消息2</li> <li>消息3</li> <li>消息4</li> </ul> </div> </template> <script> export default { name: "HomeMessage" } </script> <style scoped> </style>
-
-
在
index.js
中添加映射关系-
export default new Router({ // 配置路由和组件之间的应用关系 routes: [ /*一个映射关系一个对象*/ { path: '/HelloWorld', name: 'HelloWorld', component: HelloWorld, // children属性 children: [ { path: '', redirect: 'news', }, { path: 'news', component: HomeNews, }, { path: 'message', component: HomeMessage, }, ], }, { path: '/about', name: 'about', component: about, }, { path: '/user/:userId', name: 'user', component: user, } ], mode: 'history' })
-
-
在
HelloWorld.vue
配置-
<template> <div> <h2>Hello World</h2> <router-link to="/HelloWorld/news">新闻</router-link> <router-link to="/HelloWorld/message">消息</router-link> <router-view></router-view> </div> </template> <script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
-
16.4.9 vue-router的参数传递
- 传递参数主要有两种类型:
params
和query
params
的类型:- 配置路由格式:
/router/:id
- 传递的方式: 在
path
后面跟上对应的值 - 传递后形成的路径:
/router/123
,/router/abc
- 配置路由格式:
query
的类型:- 配置路由格式:
/router,
也就是普通配置 - 传递的方式: 对象中使用
query
的key
作为传递方式 - 传递后形成的路径:
/router?id=123
,/router?id=abc
- 配置路由格式:
query
类型的例子
-
新建一个
Profile.vue
文件-
<template> <div> <h2>我是Profile组件</h2> </div> </template> <script> export default { name: "Profile" } </script> <style scoped> </style>
-
-
index.js
配置路由-
/*配置路由的相关信息*/ import Vue from 'vue' import Router from 'vue-router' const profile = () => import('../components/Profile') // 1.通过Vue.use(插件) 安装插件 Vue.use(Router) // 2. new Router({}) 创建 VueRouter对象 3. export 导出路由 export default new Router({ // 配置路由和组件之间的应用关系 routes: [ /*一个映射关系一个对象*/ { path: '/profile', component: profile, } ], mode: 'history' })
-
-
App.vue
编写页面-
<template> <div id="app"> <router-link to="/HelloWorld">HelloWorld</router-link> <router-link to="/about">about</router-link> <router-link :to="'/user/'+userId">user</router-link> <router-link :to="{ path: '/profile', query: {name: '刘云杰', age: 18, height: 188}}"> 档案</router-link> <!-- <router-view> 作用是占位 页面要渲染在哪个位置 --> <router-view></router-view> </div> </template>
-
-
Profile.vue
取出传递过来的数据-
<template> <div> <h2>我是Profile组件</h2> <p></p> <p></p> <p></p> <p></p> </div> </template> <script> export default { name: "Profile" } </script> <style scoped> </style>
-
16.4.10 $route
和 $router
是有区别的
$route
和 $router
是有区别的:
$router
为VueRouter
实例,想要导航到不同URL
,则使用$router.push
方法$route
为当前router
跳转对象里面可以获取name
、path
、query
、params
等
16.4.11 导航守卫
vue-router
提供的导航守卫主要用来监听路由的进入和离开。vue-router
提供了beforeEach
和afterEach
的钩子函数, 它们会在路由即将改变前和改变后触发.
用处:改变页面的标题
- 可以利用
beforeEach
来完成标题的修改.- 首先, 我们可以在钩子当中定义一些标题, 可以利用
meta
来定义 - 其次, 利用导航守卫,修改我们的标题.
- 首先, 我们可以在钩子当中定义一些标题, 可以利用
-
定义标题:
-
修改标题:
** 导航钩子的三个参数解析: 1. to: 即将要进入的目标的路由对象. 2. from: 当前导航即将要离开的路由对象. 3. next: 调用该方法后, 才能进入下一个钩子.
- 补充一:如果是后置钩子, 也就是
afterEach
, 不需要主动调用next()
函数. - 补充二: 上面我们使用的导航守卫, 被称之为全局守卫.
- 路由独享的守卫.
- 组件内的守卫.
官网链接:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E8%B7%AF%E7%94%B1%E7%8B%AC%E4%BA%AB%E7%9A%84%E5%AE%88%E5%8D%AB。
16.4.12 vue-router中的keep-alive
keep-alive
是Vue
内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。- 它们有两个非常重要的属性:
include
- 字符串或正则表达,只有匹配的组件会被缓存exclude
- 字符串或正则表达式,任何匹配的组件都不会被缓存
router-view
也是一个组件,如果直接被包在keep-alive
里面,所有路径匹配到的视图组件都会被缓存:
未使用
keep-alive
之前
-
在
HelloWorld.vue
中添加如下代码-
<script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } }, created() { console.log('Hello World created'); }, destroyed() { console.log('Hello World destroyed'); }, } </script>
-
每次都是创建新的实例
-
使用
keep-alive
之后
-
App.vue
-
<template> <div id="app"> <router-link to="/HelloWorld">HelloWorld</router-link> <router-link to="/about">about</router-link> <router-link :to="'/user/'+userId">user</router-link> <router-link :to="{ path: '/profile', query: {name: '刘云杰', age: 18, height: 188}}"> 档案</router-link> <!-- <router-view> 作用是占位 页面要渲染在哪个位置 --> <keep-alive> <router-view></router-view> </keep-alive> <!-- <router-view></router-view>--> </div> </template
-
-
不会频繁创建与销毁
17. tabbar案例
17.1 基本结构的搭建
-
新建项目
vue init webpack 16-tabbar
-
编写前端基本样式
-
body{ padding: 0; margin: 0; }
-
-
App.vue
文件中引入样式-
<template> <div id="app"> <div id="tab-bar"> <div class="tab-bar-item">首页</div> <div class="tab-bar-item">分类</div> <div class="tab-bar-item">购物车</div> <div class="tab-bar-item">我的</div> </div> </div> </template> <script> export default { name: 'App', } </script> <style> @import "assets/css/base.css"; #tab-bar { display: flex; background: #f6f6f6; position: fixed; left: 0; right: 0; bottom: 0; box-shadow: 0px -2px 1px rgba(100,100,100,0.1); } .tab-bar-item { flex: 1; text-align: center; height: 49px; } </style>
-
17.2 TabBar和TabBarItem组件封装
-
新建
TabBarItem.vue
将图标和文字进行封装-
<template> <div class="tab-bar-item"> <slot name="item-icon"></slot> <slot name="item-text"></slot> </div> </template> <script> export default { name: "TabBarItem" } </script> <style scoped> .tab-bar-item { flex: 1; text-align: center; height: 49px; font-size: 14pt; } .tab-bar-item img { width: 24px; height: 24px; margin-top: 30px; vertical-align: middle; margin-bottom: 2px; } </style>
-
-
使用
TabBar.vue
将底部导航栏进行封装-
<template> <div id="tab-bar"> <slot></slot> </div> </template> <script> export default { name: "TabBar", } </script> <style scoped> #tab-bar { /* 本身的样式 */ background-color: #f6f6f6; height: 49px; border-top: 1px solid #eee; box-shadow: 0px -1px 1px rgba(150,150,150,.08); /* 定位相关 */ position: fixed; left: 0; right: 0; bottom: 0; /* 利用flex进行布局 */ display: flex; text-align: center; } </style>
-
-
将上面封装完成以后,只需要在
App.vue
写入相关数据即可-
<template> <div id="app"> <TabBar> <TabBarItem> <img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="首页"> <div slot="item-text">首页</div> </TabBarItem> <TabBarItem> <img slot="item-icon" src="./assets/img/tabbar/category.svg" alt="分类"> <div slot="item-text">分类</div> </TabBarItem> <TabBarItem> <img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt="购物车"> <div slot="item-text">购物车</div> </TabBarItem> <TabBarItem> <img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt="我的"> <div slot="item-text">我的</div> </TabBarItem> </TabBar> </div> </template> <script> import TabBar from "./components/tabbar/TabBar"; import TabBarItem from "./components/tabbar/TabBarItem"; export default { name: 'App', components: { TabBar, TabBarItem, } } </script> <style> @import "assets/css/base.css"; </style>
-
17.3 给 TabBarItem传入active图片
思路:添加
isActive
属性,true
显示active
图片;false
不显示active
图片。
TabBarItm.vue
<template>
<div class="tab-bar-item">
<div v-if="!isActive"><slot name="item-icon"></slot></div>
<div v-else-if="isActive"><slot name="item-icon-active"></slot></div>
<div :class="{'active': isActive}"><slot name="item-text"></slot></div>
</div>
</template>
<script>
export default {
name: "TabBarItem",
data() {
return {
isActive: true,
}
}
}
</script>
<style scoped>
.tab-bar-item {
flex: 1;
text-align: center;
height: 49px;
font-size: 14pt;
}
.tab-bar-item img {
width: 24px;
height: 24px;
margin-top: 3px;
vertical-align: middle;
margin-bottom: 2px;
}
.active {
color: red;
}
</style>
App.vue
<template>
<div id="app">
<TabBar>
<TabBarItem>
<img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="首页">
<img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt="首页">
<div slot="item-text">首页</div>
</TabBarItem>
<TabBarItem>
<img slot="item-icon" src="./assets/img/tabbar/category.svg" alt="分类">
<img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt="分类">
<div slot="item-text">分类</div>
</TabBarItem>
<TabBarItem>
<img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt="购物车">
<img slot="item-icon-active" src="./assets/img/tabbar/shopcart_active.svg" alt="购物车">
<div slot="item-text">购物车</div>
</TabBarItem>
<TabBarItem>
<img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt="我的">
<img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt="我的">
<div slot="item-text">我的</div>
</TabBarItem>
</TabBar>
</div>
</template>
<script>
import TabBar from "./components/tabbar/TabBar";
import TabBarItem from "./components/tabbar/TabBarItem";
export default {
name: 'App',
components: {
TabBar,
TabBarItem,
}
}
</script>
<style>
@import "assets/css/base.css";
</style>
17.4 TabBarItem和路由结合
-
为每一个页面添加对应的
.vue
文件 -
配置路由
-
import Vue from "vue"; import VueRouter from "vue-router"; const home = ()=>import('../views/home/Home') const category = ()=>import('../views/category/Category') const profile = ()=>import('../views/profile/Profile') const card = ()=>import('../views/shopcard/ShopCard') /*1 安装插件*/ Vue.use(VueRouter) /*2 创建路由对象*/ const routes = [ { path: '', redirect: '/home', }, { path: '/home', component: home, }, { path: '/category', component: category, }, { path: '/profile', component: profile, }, { path: '/card', component: card, }, ] const router = new VueRouter({ routes, mode: 'history', }) /*3 导出router*/ export default router
-
-
要想知道跳转到哪个页面,需要监听
click
事件-
App.vue
-
<template> <div id="app"> <router-view></router-view> <TabBar> <TabBarItem path="/home"> <img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="首页"> <img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt="首页"> <div slot="item-text">首页</div> </TabBarItem> <TabBarItem path="/category"> <img slot="item-icon" src="./assets/img/tabbar/category.svg" alt="分类"> <img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt="分类"> <div slot="item-text">分类</div> </TabBarItem> <TabBarItem path="/card"> <img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt="购物车"> <img slot="item-icon-active" src="./assets/img/tabbar/shopcart_active.svg" alt="购物车"> <div slot="item-text">购物车</div> </TabBarItem> <TabBarItem path="/profile"> <img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt="我的"> <img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt="我的"> <div slot="item-text">我的</div> </TabBarItem> </TabBar> </div> </template>
-
TabBarItem.vue
-
<template> <div class="tab-bar-item" @click="itemClick"> <div v-if="!isActive"><slot name="item-icon"></slot></div> <div v-else-if="isActive"><slot name="item-icon-active"></slot></div> <div :class="{'active': isActive}"><slot name="item-text"></slot></div> </div> </template> <script> export default { name: "TabBarItem", props: { path: String }, data() { return { isActive: true, } }, methods: { itemClick() { this.$router.push(this.path).catch((error)=>error); } } } </script> <style scoped> .tab-bar-item { flex: 1; text-align: center; height: 49px; font-size: 14pt; } .tab-bar-item img { width: 24px; height: 24px; margin-top: 3px; vertical-align: middle; margin-bottom: 2px; } .active { color: red; } </style>
-
17.5 TabBarItem颜色动态控制
-
在
TabBarItem.vue
中添加计算属性-
<script> export default { name: "TabBarItem", props: { path: String, activeColor: { type: String, default: 'red', } }, data() { return { // isActive: true, } }, computed: { isActive() { return this.$route.path.indexOf(this.path) !== -1; }, }, methods: { itemClick() { this.$router.push(this.path).catch((error) => error); } } } </script>
-
-
自定义修改导航栏的颜色
-
App.vue
-
<template> <div id="app"> <router-view></router-view> <TabBar> <TabBarItem path="/home" activeColor="pink"> <img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="首页"> <img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt="首页"> <div slot="item-text">首页</div> </TabBarItem> <TabBarItem path="/category" activeColor="pink"> <img slot="item-icon" src="./assets/img/tabbar/category.svg" alt="分类"> <img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt="分类"> <div slot="item-text">分类</div> </TabBarItem> <TabBarItem path="/card" activeColor="pink"> <img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt="购物车"> <img slot="item-icon-active" src="./assets/img/tabbar/shopcart_active.svg" alt="购物车"> <div slot="item-text">购物车</div> </TabBarItem> <TabBarItem path="/profile" activeColor="pink"> <img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt="我的"> <img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt="我的"> <div slot="item-text">我的</div> </TabBarItem> </TabBar> </div> </template>
-
TabBarItem.vue
-
<template> <div class="tab-bar-item" @click="itemClick"> <div v-if="!isActive"> <slot name="item-icon"></slot> </div> <div v-else-if="isActive"> <slot name="item-icon-active"></slot> </div> <div :style="itemColor"> <slot name="item-text"></slot> </div> </div> </template> <script> export default { name: "TabBarItem", props: { path: String, activeColor: { type: String, default: 'red', } }, data() { return { // isActive: true, } }, computed: { isActive() { return this.$route.path.indexOf(this.path) !== -1; }, itemColor() { return this.isActive ? {'color': this.activeColor} : {}; } }, methods: { itemClick() { this.$router.push(this.path).catch((error) => error); } } } </script> <style scoped> .tab-bar-item { flex: 1; text-align: center; height: 49px; font-size: 14pt; } .tab-bar-item img { width: 24px; height: 24px; margin-top: 3px; vertical-align: middle; margin-bottom: 2px; } </style>
-
18. Proimse
18.1 Promise的介绍和基本使用
-
Promise是做什么的?
- ==Promise是异步编程的一种解决方案。==
-
定时器的异步操作
-
方式一:定时器模拟
- ```html
- 使用setTimeout setTimeout(()=>{ console.log(‘Hello World’); }, 1000); </script> ```
-
方式二:Promise方法
-
// Promise()的参数是一个函数,这个函数有两个参数(resolve, reject) // resolve reject也是函数 new Promise((resolve, reject)=>{ // 第一次网络请求的代码 setTimeout(()=>{ resolve() // resolve()会执行then }, 1000); }).then(()=>{ // 第一次拿到结果的处理代码 console.log('Hello World1'); console.log('Hello World2'); console.log('Hello World3'); console.log('Hello World4'); console.log('Hello World5'); console.log('Hello World6'); return new Promise((resolve, reject)=>{ // 第二次网络请求的代码 setTimeout(()=>{ resolve() }, 1000) }).then(()=>{ // 第二次拿到结果的处理代码 console.log('Hello Vuejs'); console.log('Hello Vuejs'); console.log('Hello Vuejs'); console.log('Hello Vuejs'); console.log('Hello Vuejs'); console.log('Hello Vuejs'); return new Promise((resolve, reject)=>{ // 第三次网络请求的代码 setTimeout(()=>{ resolve() }, 1000) }).then(()=>{ // 第三次拿到结果的处理代码 console.log('Hello Python'); console.log('Hello Python'); console.log('Hello Python'); console.log('Hello Python'); console.log('Hello Python'); console.log('Hello Python'); }) }) }) </script>
-
-
什么时候会用到 Promise
- 一般情况是有异步操作时,使用
Promise
对异步操作进行封装。 new Promise(resolve, reject)
;- 参数
resolve
和reject
同样是函数。- 异步操作成功的时候调用
resolve
函数,执行resolve
会执行then
; - 异步操作失败的时候调用
reject
函数,执行reject
会执行catch
。
- 异步操作成功的时候调用
- 一般情况是有异步操作时,使用
18.2 Promise的三种状态
异步操作之后会有三种状态:
ending
:等待状态,比如正在进行网络请求,或者定时器没有到时间。fulfill
:满足状态,当我们主动回调了resolve
时,就处于该状态,并且会回调.then()
reject
:拒绝状态,当我们主动回调了reject
时,就处于该状态,并且会回调.catch()
其实
.then
可以有两个参数(函数1,函数2)
,两个参数同样是函数。当执行resolve
的时候调用函数1
,执行reject
的时候调用函数2
。
<script>
new Promise((resolve, reject)=>{
resolve('Hello');
}).then((data)=>{
console.log(data);
}), (error)=>{
console.log(error);
})
</script>
<script>
new Promise((resolve, reject)=>{
// resolve('Hello');
reject('出现错误');
}).then((data)=>{
console.log(data);
}, (error)=>{
console.log(error);
})
</script>
18.3 Promise的链式调用
-
全写
-
<script> new Promise(((resolve, reject) => { setTimeout(()=>{ resolve('aaa'); }, 1000) })).then((data) => { console.log(data); return new Promise(resolve => { resolve(data + '111'); }).then(data=>{ console.log(data); return new Promise(resolve => { resolve(data + '222'); }).then(data=>{ console.log(data); }) }) }) </script>
-
-
简写
-
<script> new Promise((resolve, reject) => { setTimeout(()=>{ resolve('aaa'); }, 1000) }).then((data) => { console.log(data); return Promise.resolve(data + '111') }).then(data=>{ console.log(data); return Promise.resolve(data + '222') }).then(data=>{ console.log(data); }) </script>
-
-
再次简写
-
<script> new Promise((resolve, reject) => { setTimeout(()=>{ resolve('aaa'); }, 1000) }).then((data) => { console.log(data); return data + '111' }).then(data=>{ console.log(data); return data + '222' }).then(data=>{ console.log(data); }) </script>
-
18.4 Promise的all方法
Promise.all()
参数是一个可迭代的对象,例如数组。
<script>
Promise.all([
{异步请求1},
{异步请求2},
...,
{异步请求n}
]).then(()=>{})
</script>
只有当 参数里面所有的异步请求都执行完以后,才会执行
.then
中的方法
19. Vuex
19.1 Vuex概念和作用解析
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式。它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
状态管理 是什么?
- 状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
- 其实,你可以简单的将其看成 把需要多个组件共享的变量全部存储在一个对象里面。
- 然后,将这个对象放在顶层的
Vue
实例中,让其他组件可以使用。 - 那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
有什么状态时需要我们在多个组件间共享的呢?
- 如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
- 比如用户的登录状态、用户名称、头像、地理位置信息等等。
- 比如商品的收藏、购物车中的物品等等。
- 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的(待会儿我们就可以看到代码了,莫着急)。
19.2 单界面到多界面的状态切换
19.2.1 单界面的状态切换
State
:不用多说,就是我们的状态。(你姑且可以当做就是data
中的属性)View
:视图层,可以针对State
的变化,显示不同的信息。(这个好理解吧?)Actions
:这里的Actions
主要是用户的各种操作:点击、输入等等,会导致状态的改变。
App,vue
<template>
<div id="app">
<h2></h2>
<h2></h2>
<button @click="counter++">加一</button>
<button @click="counter--">减一</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
message: '我是App的组件',
counter: 0,
}
}
}
</script>
<style>
</style>
19.2.2 多页面的状态切换
功能:
HelloVue
也想使用App.vue
中的counter
-
安装
Vuex
-
npm install vuex --save
-
-
新建
store
文件夹以及index.js
-
index.js
-
import Vue from "vue" import Vuex from 'vuex' // 1. 安装插件 Vue.use(Vuex) // 2. 创建对象 const store = new Vuex.Store({ /*一共有以下5个参数,每个参数都是对象*/ state:{ counter: 1000, }, mutations: {}, actions: {}, getters: {}, modules: {}, }) // 3.导出 export default store
-
-
App.vue
以及HelloVue.vue
中引入-
App.vue
-
<template> <div id="app"> <h2></h2> <h2></h2> <button @click="$store.state.counter++">加一</button> <button @click="$store.state.counter--">减一</button> <h2>HelloVue</h2> <HelloVue></HelloVue> <!-- 父子组件的方式 <HelloVue :counter="counter"></HelloVue>--> </div> </template> <script> import HelloVue from "./components/HelloVue"; export default { name: 'App', components: { HelloVue, }, data() { return { message: '我是App的组件', counter: 0, } } } </script> <style> </style>
-
HelloVue.vue
-
<template> <div> <h2></h2> </div> </template> <script> export default { name: "HelloVue", } </script> <style scoped> </style>
-
19.3 Vuex的基本使用
修改
Vuex
中属性的值 必须通过
mutations
中的方法实现
-
index.js
-
import Vue from "vue" import Vuex from 'vuex' // 1. 安装插件 Vue.use(Vuex) // 2. 创建对象 const store = new Vuex.Store({ /*一共有以下5个参数,每个参数都是对象*/ state:{ // 状态 counter: 1000, }, mutations: { // 方法 默认有一个参数state increment(state) { state.counter++; }, decrement(state) { state.counter--; } }, actions: {}, getters: {}, modules: {}, }) // 3.导出 export default store
-
-
App.vue
-
<template> <div id="app"> <h2></h2> <h2></h2> <button @click="addition">加一</button> <button @click="substraction">减一</button> <h2>HelloVue</h2> <HelloVue></HelloVue> <!-- 父子组件的方式 <HelloVue :counter="counter"></HelloVue>--> </div> </template> <script> import HelloVue from "./components/HelloVue"; export default { name: 'App', components: { HelloVue, }, data() { return { message: '我是App的组件', counter: 0, } }, methods: { addition() { this.$store.commit('increment'); }, substraction() { this.$store.commit('decrement'); }, } } </script> <style> </style>
-
19.4 Vuex核心概念
State
Getters
Mutation
Action
Module
19.4.1 State
单一状态树
单一状态树:
英文名称是 Single Source of Truth
,也可以翻译成单一数据源。
- 如果你的状态信息是保存到多个
Store
对象中的,那么之后的管理和维护等等都会变得特别困难。 - 所以
Vuex
也使用了单一状态树来管理应用层级的全部状态。 - 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护
19.4.2 Getters
的使用详情
需要从
store
中获取一些state
变异后的状态。类似于 计算属性。
import Vue from "vue"
import Vuex from 'vuex'
// 1. 安装插件
Vue.use(Vuex)
// 2. 创建对象
const store = new Vuex.Store({
/*一共有以下5个参数,每个参数都是对象*/
state: {
// 状态
counter: 1000,
student: [
{id: 110, name: 'lyj0', age: 18},
{id: 111, name: 'lyj1', age: 19},
{id: 112, name: 'lyj2', age: 20},
{id: 113, name: 'lyj3', age: 21},
],
},
mutations: {
// 方法 默认有一个参数state
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
}
},
actions: {},
getters: {
powerCount(state) {
return state.counter * state.counter;
},
more20Stu(state) {
return state.student.filter(s=>s.age>=20);
},
// getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.
more20StuLength(state, getters) {
return getters.more20Stu.length;
}
},
modules: {},
})
// 3.导出
export default store
App.vue
<template>
<div id="app">
<h2></h2>
<h2></h2>
<button @click="addition">加一</button>
<button @click="substraction">减一</button>
<h2>----------------App内容:getters使用--------------------</h2>
<h3></h3>
<h3></h3>
<h3></h3>
<h2>--------------------HelloVue-----------------------</h2>
<HelloVue></HelloVue>
</div>
</template>
向
getters
传递参数
index.js
const store = new Vuex.Store({
/*一共有以下5个参数,每个参数都是对象*/
state: {
// 状态
counter: 1000,
student: [
{id: 110, name: 'lyj0', age: 18},
{id: 111, name: 'lyj1', age: 19},
{id: 112, name: 'lyj2', age: 20},
{id: 113, name: 'lyj3', age: 21},
],
},
mutations: {
// 方法 默认有一个参数state
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
}
},
actions: {},
getters: {
powerCount(state) {
return state.counter * state.counter;
},
more20Stu(state) {
return state.student.filter(s=>s.age>=20);
},
more20StuLength(state, getters) {
return getters.more20Stu.length;
},
moreAgeStu(state) {
return function (age) {
return state.student.filter(s=>s.age>=age);
}
},
},
modules: {},
})
App.vue
<template>
<div id="app">
<h2></h2>
<h2></h2>
<button @click="addition">加一</button>
<button @click="substraction">减一</button>
<h2>----------------App内容:getters使用--------------------</h2>
<h3></h3>
<h3></h3>
<h3></h3>
<h3></h3>
<h2>--------------------HelloVue-----------------------</h2>
<HelloVue></HelloVue>
<!-- 父子组件的方式 <HelloVue :counter="counter"></HelloVue>-->
</div>
</template>
19.4.3 mutations
状态更新
-
Vuex
的store
状态的更新唯一方式:提交Mutation。 -
Mutations
主要包括两部分:- 字符串的事件类型(type)
- 一个回调函数(handler),该回调函数的第一个参数就是
state
。
-
mutations
的定义方式-
mutations: { // 方法 默认有一个参数state increment(state) { state.counter++; }, decrement(state) { state.counter--; } },
-
-
mutation
的更新方式-
methods: { addition() { this.$store.commit('increment'); }, substraction() { this.$store.commit('decrement'); }, }
-
向
mutation
传递参数
-
App.vue
-
methods: { addition() { this.$store.commit('increment'); }, substraction() { this.$store.commit('decrement'); }, addCount(count) { this.$store.commit('incCount', count); }, addStudent() { const student = {id: 115, name: '刘云杰', age: 20}; this.$store.commit('addStudent', student); } }
-
-
index.js
-
mutations: { // 方法 默认有一个参数state increment(state) { state.counter++; }, decrement(state) { state.counter--; }, incCount(state, count) { state.counter += count; }, addStudent(state, stu) { state.student.push(stu); } },
-
mutation
的提交风格
addStudent() {
// 1. 普通的提交风格
const student = {id: 115, name: '刘云杰', age: 20};
// this.$store.commit('addStudent', student);
//2. 特殊的提交风格
this.$store.commit({
type: 'addStudent',
student,
})
}
这种情况下,index.js
中的 addStudent()
方法会接收到一个 对象。
19.4.4 mutations
的响应规则
Vuex
的store
中的state
是响应式的, 当state
中的数据发生改变时,Vue
组件会自动更新.- 这就要求我们必须遵守一些
Vuex
对应的规则:- 提前在
store
中初始化好所需的属性. - 当给
store
中的对象添加新属性时, 使用下面的方式:- 方式一: 使用
Vue.set(obj, 'newProp', 123)
- 方式二: 用新对象给旧对象重新赋值
- 方式一: 使用
- 提前在
19.4.5 actions
的使用详解
Vuex
要求我们Mutation
中的方法必须是同步方法.- 主要的原因是当我们使用
devtools
时,devtools
可以帮助我们捕捉mutation
的快照. - 但是如果是异步操作, 那么
devtools
将不能很好的追踪这个操作什么时候会被完成.
- 主要的原因是当我们使用
- 异步操作必须要在
Action
中进行
context
是什么?
context
是和store
对象具有相同方法和属性的对象.- 也就是说,我们可以通过
context
去进行commit
相关的操作, 也可以获取context.state
等.
19.4.6 modules
的使用详情
Vue
使用单一状态树,那么也意味着很多状态都会交给Vuex
来管理.- 当应用变得非常复杂时,
store
对象就有可能变得相当臃肿. - 为了解决这个问题,
Vuex
允许我们将store
分割成模块(Module
), 而每个模块拥有自己的state
、mutations
、actions
、getters
等。
19.5 文件夹目录结构
20. 网络请求模块-Axios
20.1 请求方式
axios
支持多种请求方式:
axios(config)
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
举例
axios(config)
默认使用get
请求,可以通过methods
修改。
import Vue from 'vue'
import App from './App'
import axios from 'axios'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
render: h => h(App)
})
axios({
url: 'http://123.207.32.32:8000/home/multidata',
// methods: 'post', 请求方式
}).then(res=>{
console.log(res);
})
20.2 axios 并发请求
有时候, 我们可能需求同时发送两个请求
- 使用
axios.all
, 可以放入多个请求的数组. axios.all([])
返回的结果是一个数组,使用axios.spread
可将数组[res1,res2]
展开为res1, res2
import Vue from 'vue'
import App from './App'
// import router from './router'
import axios from 'axios'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
// router,
// axios,
render: h => h(App)
})
/* 1基本使用*/
// axios({
// url: 'http://123.207.32.32:8000/home/multidata',
// // methods: 'post', 请求方式
// }).then(res => {
// console.log(res);
// })
/*2 并发请求*/
axios.all([axios({
url: 'http://123.207.32.32:8000/home/multidata',
}), axios({
url: 'http://123.207.32.32:8000/home/data',
params: {
type: 'sell',
page: 2,
}
})]).then(axios.spread((res1, res2)=>{
console.log(res1);
console.log(res2);
}))
20.3 axios的配置信息相关
在上面的示例中, 我们的 BaseURL
是固定的
- 事实上, 在开发中可能很多参数都是固定的.
- 这个时候我们可以进行一些抽取, 也可以利用
axios
的全局配置
// 公共配置
axios.defaults.baseURL = 'http://123.207.32.32:8000',
axios.defaults.timeout = 1000
/*2 并发请求*/
axios.all([axios({
url: '/home/multidata',
}), axios({
url: '/home/data',
params: {
type: 'sell',
page: 2,
}
})]).then(axios.spread((res1, res2)=>{
console.log(res1);
console.log(res2);
}))
20.4 axios的实例和模块封装
-
新建
request.js
存储所有axios
代码-
import axios from "axios"; export function request(config) { // 1. 创建 axios 实例 // return new Promise的缩写 const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) return instance(config) /* return new Promise(((resolve, reject) => { // 1. 创建 axios 实例 const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) // 发送网络请求 instance(config) .then(res=>{ resolve(res) }) .catch(err=>{ reject(err) }) }))*/ }
-
-
main.js
文件处理-
import Vue from 'vue' import App from './App' // import router from './router' import axios from 'axios' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', // router, // axios, render: h => h(App) }) // 封装 request 模块 import {request} from "./network/request"; request({ url: '/home/multidata', }).then(res=>{ console.log(res); }).catch(err=>{ console.log(err); })
-
20.5 拦截器
axios
提供了拦截器,用于我们在发送每次请求或者得到相应后,进行对应的处理。
- 如何使用拦截器?
- 拦截器可以做的事情。
- 拦截成功
- 拦截失败
// 2. axios 的拦截器
/*拦截请求*/
/*请求拦截的作用:
* 1. config中的一些信息不符合服务器的要求
* 2. 每次发送网络请求时,希望在界面中显示一个请求的图标
* 3. 某些网络请求(比如登录 token)必须携带一些特殊的信息
* */
axios.interceptors.request.use(config=>{
console.log('axios拦截请求成功');
}, error => {
console.log('axios拦截请求失败');
});
/*拦截响应*/
axios.interceptors.response.use(result=>{
console.log('axios拦截响应成功');
}, error => {
console.log('axios拦截响应失败');
});