vue

Vue 的核心知识

Vue 的基本认识

官网

英文官网: https://vuejs.org/
中文官网: https://cn.vuejs.org/

介绍描述

  1. 渐进式 JavaScript 框架

  2. 作者: 尤雨溪(一位华裔前 Google 工程师)

  3. 作用: 动态构建用户界面

Vue 的特点

  1. 遵循 MVVM 模式

  2. 编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发

  3. 它本身只关注 UI, 可以轻松引入 vue 插件或其它第三库开发项目

与其它前端 JS 框架的关联

  1. 借鉴 angular 的模板和数据绑定技术

  2. 借鉴 react 的组件化和虚拟 DOM 技术

Vue 扩展插件

  1. vue-cli: vue 脚手架

  2. vue-resource(axios): ajax 请求

  3. vue-router: 路由

  4. vuex: 状态管理

  5. vue-lazyload: 图片懒加载

  6. vue-scroller: 页面滑动相关

  7. mint-ui: 基于 vue 的 UI 组件库(移动端)

  8. element-ui: 基于 vue 的 UI 组件库(PC 端)

Vue 的基本使用

效果 (01_HelloWorld/test.html)

vue的基本使用

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>01_HelloWorld</title>
</head>
<body>

<!--
1. 引入Vue.js
2. 创建Vue对象
el : 指定根element(选择器)
data : 初始化数据(页面可以访问)
3. 双向数据绑定 : v-model
4. 显示数据 : {{xxx}}
5. 理解vue的mvvm实现
-->

<!--模板-->
<div id="test">
<input type="text" v-model="msg"><br><!--指令-->
<input type="text" v-model="msg"><!--指令-->
<p>hello {{msg}}</p><!--大括号表达式-->
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
const vm = new Vue({ // 配置对象 options
// 配置选项(option)
el: '#test', // element: 指定用vue来管理页面中的哪个标签区域
data: {
msg: ''
}
})
</script>
</body>
</html>

理解 Vue 的 MVVM

vue的MVVM

模板语法

效果

vue的模板语法

模板的理解

  1. 动态的 html 页面

  2. 包含了一些 JS 语法代码

​ a. 双大括号表达式

​ b. 指令(以 v-开头的自定义标签属性)

双大括号表达式

  1. 语法:

  2. 功能: 向页面输出数据

  3. 可以调用对象的方法

指令一: 强制数据绑定

  1. 功能: 指定变化的属性值

  2. 完整写法: v-bind:xxx=‘yyy’ //yyy 会作为表达式解析执行

  3. 简洁写法: :xxx=‘yyy’

指令二: 绑定事件监听

  1. 功能: 绑定指定事件名的回调函数

  2. 完整写法:

1
2
3
v-on:keyup='xxx'
v-on:keyup='xxx(参数)'
v-on:keyup.enter='xxx'
  1. 简洁写法:
1
2
@keyup='xxx'
@keyup.enter='xxx'

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>02_模板语法</title>
</head>
<body>
<!--
1. 模板的理解:
动态的html页面
包含了一些JS语法代码
大括号表达式
指令(以v-开头的自定义标签属性)
2. 双大括号表达式
语法: {{exp}} 或 {{{exp}}}
功能: 向页面输出数据
可以调用对象的方法
3. 指令一: 强制数据绑定
功能: 指定变化的属性值
完整写法:
v-bind:xxx='yyy' //yyy会作为表达式解析执行
简洁写法:
:xxx='yyy'
4. 指令二: 绑定事件监听
功能: 绑定指定事件名的回调函数
完整写法:
v-on:click='xxx'
简洁写法:
@click='xxx'
-->

<div id="app">
<h2>1. 双大括号表达式</h2>
<p>{{content}}</p>
<p>{{content.toUpperCase()}}</p>
<p v-text="content"></p> <!-- textContent -->
<p v-html="content"></p> <!-- innerHTML -->

<h2>2. 指令一: 强制数据绑定</h2>
<a href="url">访问指定站点</a><br>
<a v-bind:href="url">访问指定站点2</a><br>
<a :href="url">访问指定站点2</a><br>

<h2>3. 指令二: 绑定事件监听</h2>
<button v-on:click="test">点我</button>
<button @click="test">点我</button>

</div>


<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
content: '<a>NBA I Love This Game</a>',
url: 'http://www.baidu.com'
},
methods: {
test () {
alert('好啊!!!')
}
}
})
</script>
</body>
</html>

计算属性和监视

效果

vue的计算属性和监视

计算属性

  1. 在 computed 属性对象中定义计算属性的方法

  2. 在页面中使用来显示计算的结果

监视属性

  1. 通过 vm 对象的$watch()或 watch 配置来监视指定的属性

  2. 当属性变化时, 回调函数自动调用, 在函数内部进行计算

计算属性高级

  1. 通过 getter/setter 实现对属性数据的显示和监视

  2. 计算属性存在缓存, 多次读取只执行一次 getter 计算

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>03_计算属性和监视</title>
</head>
<body>
<!--
1. 计算属性
在computed属性对象中定义计算属性的方法
在页面中使用{{方法名}}来显示计算的结果
2. 监视属性:
通过通过vm对象的$watch()或watch配置来监视指定的属性
当属性变化时, 回调函数自动调用, 在函数内部进行计算
3. 计算属性高级:
通过getter/setter实现对属性数据的显示和监视
计算属性存在缓存, 多次读取只执行一次getter计算

getter: 属性的get方法
setter: 属性的set方法
-->
<div id="demo">
姓: <input type="text" placeholder="First Name" v-model="firstName"><br>
名: <input type="text" placeholder="Last Name" v-model="lastName"><br>
<!--fullName1是根据fistName和lastName计算产生-->
姓名1(单向): <input type="text" placeholder="Full Name1" v-model="fullName1"><br>
姓名2(单向): <input type="text" placeholder="Full Name2" v-model="fullName2"><br>
姓名3(双向): <input type="text" placeholder="Full Name3" v-model="fullName3"><br>

<p>{{fullName1}}</p>
<p>{{fullName1}}</p>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
const vm = new Vue({
el: '#demo',
data: {
firstName: 'A',
lastName: 'B',
fullName2: 'A-B'
},

// 计算属性配置: 值为对象
computed: {
// 什么时候执行:初始化显示/相关的data属性数据发生改变
// 计算并返回当前的属性值
fullName1 () { // 属性的get()。计算属性中的一个方法,方法的返回值作为属性值
console.log('fullName1()', this)
return this.firstName + '-' + this.lastName
},

fullName3: {
// 回调函数:你定义的、你没有调用、但最终它执行了
// 什么时候调用?用来做什么?
// 当获取当前属性值时自动调用, 将返回值(根据相关的其它属性数据)作为属性值
get () {
console.log('fullName3 get()')
return this.firstName + '-' + this.lastName
},
// 回调函数,当属性值发生了改变时自动调用, 监视当前属性值变化, 同步更新相关的其它属性值
set (value) {// fullName3的最新value值 A-B23
console.log('fullName3 set()', value)
// 更新firstName和lastName
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
}
},

watch: {
// 配置监视firstName
firstName: function (value) { // 相当于属性的set。fistName发生了变化,执行回调函数
console.log('watch firstName', value)
// 更新fullName2
this.fullName2 = value + '-' + this.lastName
}
}
})

// 监视lastName
vm.$watch('lastName', function (value) {
console.log('$watch lastName', value)
// 更新fullName2
this.fullName2 = this.firstName + '-' + value
})

</script>
</body>
</html>

class 与 style 绑定

理解

  1. 在应用界面中, 某个(些)元素的样式是变化的

  2. class/style 绑定就是专门用来实现动态样式效果的技术

class 绑定

  1. :class=‘xxx’

  2. 表达式是字符串: ‘classA’

  3. 表达式是对象: {classA:isA, classB: isB}

  4. 表达式是数组: [‘classA’, ‘classB’]

style 绑定

  1. :style="{ color: activeColor, fontSize: fontSize + ‘px’ }"

  2. 其中 activeColor/fontSize 是 data 属性

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>04_class与style绑定</title>
<style>
.classA {
color: red;
}

.classB {
background: blue;
}

.classC {
font-size: 20px;
}
</style>
</head>

<body>

<!--
1. 理解
在应用界面中, 某个(些)元素的样式是变化的
class/style绑定就是专门用来实现动态样式效果的技术
2. class绑定: :class='xxx'
xxx是字符串
xxx是对象
xxx是数组
3. style绑定
:style="{ color: activeColor, fontSize: fontSize + 'px' }"
其中activeColor/fontSize是data属性
-->

<div id="demo">
<h2>1. class绑定: :class='xxx'</h2>
<p :class="myClass">xxx是字符串</p>
<p :class="{classA: hasClassA, classB: hasClassB}">xxx是对象</p>
<p :class="['classA', 'classB']">xxx是数组</p>

<h2>2. style绑定</h2>
<p :style="{color:activeColor, fontSize}">:style="{ color: activeColor, fontSize: fontSize + 'px' }"</p>

<button @click="update">更新</button>

</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
myClass: 'classA',
hasClassA: true,
hasClassB: false,
activeColor: 'red',
fontSize: '20px'
},

methods: {
update() {
this.myClass = 'classB'
this.hasClassA = !this.hasClassA
this.hasClassB = !this.hasClassB
this.activeColor = 'yellow'
this.fontSize = '30px'
}
}
})
</script>
</body>

</html>

条件渲染

条件渲染指令

  1. v-if 与 v-else

  2. v-show

比较 v-if 与 v-show

  1. 如果需要频繁切换 v-show 较好

  2. 当条件不成立时, v-if 的所有子节点不会解析(项目中使用)

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>08_条件渲染</title>
</head>
<body>
<!--
1. 条件渲染指令
v-if
v-else
v-show
2. 比较v-if与v-show
如果需要频繁切换 v-show 较好
-->

<div id="demo">
<p v-if="ok">表白成功</p>
<p v-else>表白失败</p>

<hr>
<p v-show="ok">求婚成功</p>
<p v-show="!ok">求婚失败</p>

<button @click="ok=!ok">切换</button>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
ok: true,
}
})
</script>
</body>
</html>

列表渲染

  1. 列表显示指令

数组: v-for / index

对象: v-for / key

  1. 列表的更新显示

删除 item

替换 item

  1. 列表的高级处理

列表过滤

列表排序

编码

列表渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>06_列表渲染</title>
</head>
<body>

<!--
1. 列表显示
数组: v-for / index
对象: v-for / key
2. 列表的更新显示
删除item
替换item
-->

<div id="demo">
<h2>测试: v-for 遍历数组</h2>
<ul>
<li v-for="(p, index) in persons" :key="index">
{{index}}--{{p.name}}--{{p.age}}
--<button @click="deleteP(index)">删除</button>
--<button @click="updateP(index, {name:'Cat', age: 16})">更新</button>
</li>
</ul>
<button @click="addP({name: 'xfzhang', age: 18})">添加</button>

<h2>测试: v-for 遍历对象</h2>

<ul>
<li v-for="(item, key) in persons[1]" :key="key">{{key}}={{item}}</li>
</ul>

</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
// vue本身只是监视了persons的改变,没有监视数组内部数据的改变
// vue重写了数组中的一系列改变数组内部数据的方法(先调用原生,更新界面)--》数组内部改变,界面自动变化
new Vue({
el: '#demo',
data: {
persons: [
{name: 'Tom', age:18},
{name: 'Jack', age:17},
{name: 'Bob', age:19},
{name: 'Mary', age:16}
]
},

methods: {
deleteP (index) {
this.persons.splice(index, 1) // 调用了不是原生数组的splice(), 而是一个变异(重写)方法
// 1. 调用原生的数组的对应方法
// 2. 更新界面
},

updateP (index, newP) {
console.log('updateP', index, newP)
// this.persons[index] = newP // vue根本就不知道
this.persons.splice(index, 1, newP)
// this.persons = []
},

addP (newP) {
this.persons.push(newP)
}
}
})
</script>
</body>
</html>

列表的过滤和排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>06_列表渲染_过滤与排序</title>
</head>
<body>
<!--
1. 列表过滤
2. 列表排序
-->

<div id="demo">
<input type="text" v-model="searchName">
<ul>
<li v-for="(p, index) in filterPersons" :key="index">
{{index}}--{{p.name}}--{{p.age}}
</li>
</ul>
<div>
<button @click="setOrderType(2)">年龄升序</button>
<button @click="setOrderType(1)">年龄降序</button>
<button @click="setOrderType(0)">原本顺序</button>
</div>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript"> new Vue({
el: '#demo',
data: {
searchName: '',
orderType: 0, // 0代表不排序, 1代表降序, 2代表升序
persons: [
{name: 'Tom', age:18},
{name: 'Jack', age:17},
{name: 'Bob', age:19},
{name: 'Mary', age:16}
]
},

computed: {
filterPersons () {
// debugger
// 取出相关数据
const {searchName, persons, orderType} = this
let arr = [...persons]
// 过滤数组
if(searchName.trim()) {
arr = persons.filter(p => p.name.indexOf(searchName)!==-1)
}
// 排序
if(orderType) {
arr.sort(function (p1, p2) {
if(orderType===1) { // 降序
return p2.age-p1.age
} else { // 升序
return p1.age-p2.age
}

})
}
return arr
}
},

methods: {
setOrderType (orderType) {
this.orderType = orderType
}
}
})
</script>
</body>
</html>

事件处理

绑定监听:

  1. v-on:xxx=“fun”

  2. @xxx=“fun”

  3. @xxx=“fun(参数)”

  4. 默认事件形参: event

  5. 隐含属性对象: $event

事件修饰符

  1. .prevent : 阻止事件的默认行为 event.preventDefault()

  2. .stop : 停止事件冒泡 event.stopPropagation()

按键修饰符

  1. .keycode : 操作的是某个 keycode 值的键

  2. .keyName : 操作的某个按键名的键(少部分)

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>07_事件处理</title>
</head>
<body>
<!--
1. 绑定监听:
v-on:xxx="fun"
@xxx="fun"
@xxx="fun(参数)"
默认事件形参: event
隐含属性对象: $event
2. 事件修饰符:
.prevent : 阻止事件的默认行为 event.preventDefault()
.stop : 停止事件冒泡 event.stopPropagation()
3. 按键修饰符
.keycode : 操作的是某个keycode值的健
.enter : 操作的是enter键
-->

<div id="example">

<h2>1. 绑定监听</h2>
<button @click="test1">test1</button>
<button @click="test2('abc')">test2</button>
<button @click="test3('abcd', $event)">test3</button>

<h2>2. 事件修饰符</h2>
<a href="http://www.baidu.com" @click.prevent="test4">百度一下</a>
<div style="width: 200px;height: 200px;background: red" @click="test5">
<div style="width: 100px;height: 100px;background: blue" @click.stop="test6"></div>
</div>

<h2>3. 按键修饰符</h2>
<input type="text" @keyup.13="test7">
<input type="text" @keyup.enter="test7">

</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#example',
data: {

},
methods: {
test1(event) {
alert(event.target.innerHTML)
},
test2 (msg) {
alert(msg)
},
test3 (msg, event) {
alert(msg+'---'+event.target.textContent)
},

test4 () {
alert('点击了链接')
},

test5 () {
alert('out')
},
test6 () {
alert('inner')
},

test7 (event) {
console.log(event.keyCode)
alert(event.target.value)
}
}
})
</script>
</body>
</html>

表单输入绑定

使用 v-model 对表单数据自动收集

  1. text/textarea

  2. checkbox

  3. radio

  4. select

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>08_表单输入绑定</title>
</head>
<body>
<!--
1. 使用v-model(双向数据绑定)自动收集数据
text/textarea
checkbox
radio
select
-->
<div id="demo">
<form action="/xxx" @submit.prevent="handleSubmit">
<span>用户名: </span>
<input type="text" v-model="username"><br>

<span>密码: </span>
<input type="password" v-model="pwd"><br>

<span>性别: </span>
<input type="radio" id="female" value="女" v-model="sex">
<label for="female"></label>
<input type="radio" id="male" value="男" v-model="sex">
<label for="male"></label><br>

<span>爱好: </span>
<input type="checkbox" id="basket" value="basket" v-model="likes">
<label for="basket">篮球</label>
<input type="checkbox" id="foot" value="foot" v-model="likes">
<label for="foot">足球</label>
<input type="checkbox" id="pingpang" value="pingpang" v-model="likes">
<label for="pingpang">乒乓</label><br>

<span>城市: </span>
<select v-model="cityId">
<option value="">未选择</option>
<option :value="city.id" v-for="(city, index) in allCitys" :key="city.id">{{city.name}}</option>
</select><br>
<span>介绍: </span>
<textarea rows="10" v-model="info"></textarea><br><br>

<input type="submit" value="注册">
</form>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
username: '',
pwd: '',
sex: '男',
likes: ['foot'],
allCitys: [{id: 1, name: 'BJ'}, {id: 2, name: 'SS'}, {id: 3, name: 'SZ'}],
cityId: '2',
info: ''
},
methods: {
handleSubmit () {
console.log(this.username, this.pwd, this.sex, this.likes, this.cityId, this.info)
alert('提交注册的ajax请求')
}
}
})
</script>
</body>
</html>

Vue 实例生命周期

生命周期流程图

Vue 实例生命周期

vue 生命周期分析

  1. 初始化显示
  • beforeCreate()
  • created()
  • beforeMount()
  • mounted()
  1. 更新状态: this.xxx = value
  • beforeUpdate()
  • updated()
  1. 销毁 vue 实例: vm.$destory()
  • beforeDestory()
  • destoryed()

常用的生命周期方法

  1. created()/mounted(): 发送 ajax 请求, 启动定时器等异步任务

  2. beforeDestory(): 做收尾工作, 如: 清除定时器

编码

test.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>09_Vue实例_生命周期</title>
</head>
<body>
<!--
1. vue对象的生命周期
1). 初始化显示
* beforeCreate()
* created()
* beforeMount()
* mounted()
2). 更新状态
* beforeUpdate()
* updated()
3). 销毁vue实例: vm.$destory()
* beforeDestory()
* destoryed()
2. 常用的生命周期方法
created()/mounted(): 发送ajax请求, 启动定时器等异步任务
beforeDestory(): 做收尾工作, 如: 清除定时器
-->

<div id="test">
<button @click="destroyVue">destory vue</button>
<p v-if="isShow">尚硅谷IT教育</p>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#test',
data: {
isShow: true
},

mounted () {
// 执行异步任务
this.intervalId = setInterval(() => {
console.log('-----')
this.isShow = !this.isShow
}, 1000)
},

beforeDestroy() {
console.log('beforeDestroy()')
// 执行收尾的工作
clearInterval(this.intervalId)
},

methods: {
destroyVue () {
this.$destroy()
}
}
})


</script>
</body>
</html>

test2.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>09_Vue实例_生命周期</title>
</head>
<body>
<!--
1. vue对象的生命周期
1). 初始化显示
* beforeCreate()
* created()
* beforeMount()
* mounted()
2). 更新状态
* beforeUpdate()
* updated()
3). 销毁vue实例: vm.$destory()
* beforeDestory()
* destoryed()
2. 常用的生命周期方法
created()/mounted(): 发送ajax请求, 启动定时器等异步任务
beforeDestory(): 做收尾工作, 如: 清除定时器
-->

<div id="test">
<button @click="destroyVue">destory vue</button>
<p v-if="isShow">尚硅谷IT教育</p>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#test',
data: {
isShow: true
},

beforeCreate() {
console.log('beforeCreate()')
},

created() {
console.log('created()')
},

beforeMount() {
console.log('beforeMount()')
},

mounted () {
console.log('mounted()')
// 执行异步任务
this.intervalId = setInterval(() => {
console.log('-----')
this.isShow = !this.isShow
}, 1000)
},


beforeUpdate() {
console.log('beforeUpdate()')
},
updated () {
console.log('updated()')
},


beforeDestroy() {
console.log('beforeDestroy()')
// 执行收尾的工作
clearInterval(this.intervalId)
},

destroyed() {
console.log('destroyed()')
},

methods: {
destroyVue () {
this.$destroy()
}
}
})


</script>
</body>
</html>

过渡&动画

vue 动画的理解

  1. 操作 css 的 trasition 或 animation

  2. vue 会给目标元素添加/移除特定的 class

  3. 过渡的相关类名

  • xxx-enter-active: 指定显示的 transition
  • xxx-leave-active: 指定隐藏的 transition
  • xxx-enter/xxx-leave-to: 指定隐藏时的样式

Transition Diagram

基本过渡动画的编码

  1. 在目标元素外包裹

  2. 定义 class 样式

  • 指定过渡样式: transition
  • 指定隐藏时的样式: opacity/其它

编码 1

vue的过渡和动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>10_过渡&动画1</title>
<style>
/*指定过渡样式*/
.xxx-enter-active, .xxx-leave-active {
transition: opacity 1s
}
/*指定隐藏时的样式*/
.xxx-enter, .xxx-leave-to {
opacity: 0;
}


.move-enter-active {
transition: all 1s
}

.move-leave-active {
transition: all 3s
}

.move-enter, .move-leave-to {
opacity: 0;
transform: translateX(20px)
}
</style>
</head>
<body>
<!--
1. vue动画的理解
操作css的trasition或animation
vue会给目标元素添加/移除特定的class
2. 基本过渡动画的编码
1). 在目标元素外包裹<transition name="xxx">
2). 定义class样式
1>. 指定过渡样式: transition
2>. 指定隐藏时的样式: opacity/其它
3. 过渡的类名
xxx-enter-active: 指定显示的transition
xxx-leave-active: 指定隐藏的transition
xxx-enter: 指定隐藏时的样式
-->



<div id="demo">
<button @click="show = !show">Toggle</button>
<transition name="xxx">
<p v-show="show">hello</p>
</transition>
</div>

<hr>
<div id="demo2">
<button @click="show = !show">Toggle2</button>
<transition name="move">
<p v-show="show">hello</p>
</transition>
</div>


<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
show: true
}
})

new Vue({
el: '#demo2',
data: {
show: true
}
})

</script>
</body>
</html>

编码2

vue的弹跳动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>10_过渡&动画2</title>
<style>
.bounce-enter-active {
animation: bounce-in .5s;
}
.bounce-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
</style>
</head>
<body>

<div id="example-2">
<button @click="show = !show">Toggle show</button>
<br>
<transition name="bounce">
<p v-if="show" style="display: inline-block">Lorem</p>
</transition>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script>
new Vue({
el: '#example-2',
data: {
show: true
}
})
</script>
</body>
</html>

过滤器

过滤器

理解过滤器

  1. 功能: 对要显示的数据进行特定格式化后再显示

  2. 注意: 并没有改变原本的数据, 可是产生新的对应的数据

定义和使用过滤器

  1. 定义过滤器
1
2
3
4
Vue.filter(filterName,function(value[,arg1,arg2,...]){
// 进行一定的数据处理
return newValue
})
  1. 使用过滤器
1
2
<div>{{myData | filterName}}</div>
<div>{{myData | filterName(arg)}}</div>

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>11_过滤器</title>
</head>
<body>
<!--
1. 理解过滤器
功能: 对要显示的数据进行特定格式化后再显示
注意: 并没有改变原本的数据, 可是产生新的对应的数据
2. 编码
1). 定义过滤器
Vue.filter(filterName, function(value[,arg1,arg2,...]){
// 进行一定的数据处理
return newValue
})
2). 使用过滤器
<div>{{myData | filterName}}</div>
<div>{{myData | filterName(arg)}}</div>
-->
<!--需求: 对当前时间进行指定格式显示-->
<div id="test">
<h2>显示格式化的日期时间</h2>
<p>{{time}}</p>
<p>最完整的: {{time | dateString}}</p>
<p>年月日: {{time | dateString('YYYY-MM-DD')}}</p>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/moment.js/2.22.1/moment.js"></script>
<script>
// 定义过滤器
Vue.filter('dateString', function (value, format='YYYY-MM-DD HH:mm:ss') {

return moment(value).format(format);
})

new Vue({
el: '#test',
data: {
time: new Date()
},
mounted () {
setInterval(() => {
this.time = new Date()
}, 1000)
}
})
</script>
</body>
</html>

内置指令与自定义指令

常用内置指令

  1. v-text : 更新元素的 textContent

  2. v-html : 更新元素的 innerHTML

  3. v-if : 如果为 true, 当前标签才会输出到页面

  4. v-else: 如果为 false, 当前标签才会输出到页面

  5. v-show : 通过控制 display 样式来控制显示/隐藏

  6. v-for : 遍历数组/对象

  7. v-on : 绑定事件监听, 一般简写为@

  8. v-bind : 强制绑定解析表达式, 可以省略 v-bind

  9. v-model : 双向数据绑定

  10. ref : 指定唯一标识, vue 对象通过$refs属性访问这个元素对象

  11. v-cloak : 防止闪现, 与 css 配合: [v-cloak] { display: none }

自定义指令

  1. 注册全局指令
1
2
3
Vue.directive('my-directive', function(el, binding){
el.innerHTML = binding.value.toupperCase()
})
  1. 注册局部指令
1
2
3
4
5
6
7
directives : {
'my-directive' : {
bind (el, binding) {
el.innerHTML = binding.value.toupperCase()
}
}
}
  1. 使用指令
1
v-my-directive='xxx'

编码 1(内置指令)

1579919362731

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>12_指令_内置指令</title>
<style>
[v-cloak] { display: none }
</style>
</head>
<body>
<!--
常用内置指令
v-text : 更新元素的 textContent
v-html : 更新元素的 innerHTML
v-if : 如果为true, 当前标签才会输出到页面
v-else: 如果为false, 当前标签才会输出到页面
v-show : 通过控制display样式来控制显示/隐藏
v-for : 遍历数组/对象
v-on : 绑定事件监听, 一般简写为@
v-bind : 强制绑定解析表达式, 可以省略v-bind
v-model : 双向数据绑定
ref : 为某个元素注册一个唯一标识, vue对象通过$refs属性访问这个元素对象
v-cloak : 使用它防止闪现表达式, 与css配合: [v-cloak] { display: none }
-->
<div id="example">
<p v-cloak>{{content}}</p>
<p v-text="content"></p> <!--p.textContent = content-->
<p v-html="content"></p> <!--p.innerHTML = content-->
<p ref="msg">abcd</p>
<button @click="hint">提示</button>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#example',
data: {
content: '<a href="http://www.baidu.com">百度一下</a>'
},
methods: {
hint () {
alert(this.$refs.msg.innerHTML)
}
}
})
</script>
</body>
</html>

编码 2(自定义指令)

1579919384169

需求: 自定义 2 个指令

  1. 功能类型于 v-text, 但转换为全大写
  2. 功能类型于 v-text, 但转换为全小写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>12_指令_自定义指令</title>
</head>
<body>

<!--
1. 注册全局指令
Vue.directive('my-directive', function(el, binding){
el.innerHTML = binding.value.toupperCase()
})
2. 注册局部指令
directives : {
'my-directive' : {
bind (el, binding) {
el.innerHTML = binding.value.toupperCase()
}
}
}
3. 使用指令
v-my-directive='xxx'
-->
<!--
需求: 自定义2个指令
1. 功能类型于v-text, 但转换为全大写
2. 功能类型于v-text, 但转换为全小写
-->

<div id="test">
<p v-upper-text="msg"></p>
<p v-lower-text="msg"></p>
</div>

<div id="test2">
<p v-upper-text="msg"></p>
<p v-lower-text="msg"></p>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
// 注册一个全局指令
// el: 指令所在的标签对象
// binding: 包含指令相关数据的容器对象
Vue.directive('upper-text', function (el, binding) {
console.log(el, binding)
el.textContent = binding.value.toUpperCase()
})
new Vue({
el: '#test',
data: {
msg: "I Like You"
},
// 注册局部指令
directives: {
'lower-text'(el, binding) {
console.log(el, binding)
el.textContent = binding.value.toLowerCase()
}
}

})

new Vue({
el: '#test2',
data: {
msg: "I Like You Too"
}
})
</script>
</body>
</html>

自定义插件

说明

  1. Vue 插件是一个包含 install 方法的对象

  2. 通过 install 方法给 Vue 或 Vue 实例添加方法, 定义全局指令等

test.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>17_插件</title>
</head>
<body>

<div id="test">
<p v-my-directive="msg"></p>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="vue-myPlugin.js"></script>
<script type="text/javascript">
// 声明使用插件(安装插件: 调用插件的install())
Vue.use(MyPlugin) // 内部会调用插件对象的install()

const vm = new Vue({
el: '#test',
data: {
msg: 'HaHa'
}
})
Vue.myGlobalMethod()
vm.$myMethod()

new Object()
</script>
</body>
</html>

vue-myPlugin.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(function (window) {
const MyPlugin = {}
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
console.log('Vue函数对象的myGlobalMethod()')
}

// 2. 添加全局资源
Vue.directive('my-directive',function (el, binding) {
el.textContent = 'my-directive----'+binding.value
})

// 4. 添加实例方法
Vue.prototype.$myMethod = function () {
console.log('vm $myMethod()')
}
}
window.MyPlugin = MyPlugin
})(window)

vue 组件化编码

使用 vue-cli 创建模板项目

说明

  1. vue-cli 是 vue 官方提供的脚手架工具

  2. github: https://github.com/vuejs/vue-cli

  3. 作用: 从 https://github.com/vuejs-templates 下载模板项目

创建 vue 项目

1
2
3
4
5
npm install -g vue-cli
vue init webpack vue_demo
cd vue_demo
npm install
npm run dev

访问: http://localhost:8080/

模板项目的结构

|-- build : webpack 相关的配置文件夹(基本不需要修改)
|-- dev-server.js : 通过 express 启动后台服务器
|-- config: webpack 相关的配置文件夹(基本不需要修改)
|-- index.js: 指定的后台服务的端口号和静态资源文件夹
|-- node_modules
|-- src : 源码文件夹
|-- components: vue 组件及其相关资源文件夹
|-- App.vue: 应用根主组件
|-- main.js: 应用入口 js
|-- static: 静态资源文件夹
|-- .babelrc: babel 的配置文件
|-- .eslintignore: eslint 检查忽略的配置
|-- .eslintrc.js: eslint 检查的配置
|-- .gitignore: git 版本管制忽略的配置
|-- index.html: 主页面文件
|-- package.json: 应用包配置文件
|-- README.md: 应用描述说明的 readme 文件

效果

1580615140661

项目的打包与发布

打包:

1
npm run build

发布 1: 使用静态服务器工具包

1
2
npm install -g serve
serve dist

访问: http://localhost:5000

发布 2: 使用动态 web 服务器(tomcat)

修改配置: webpack.prod.conf.js

1
2
3
output: {
publicPath: '/xxx/' //打包文件夹的名称
}

重新打包:

1
npm run build

修改 dist 文件夹为项目名称: xxx

将 xxx 拷贝到运行的 tomcat 的 webapps 目录下

访问: http://localhost:8080/xxx

eslint

说明

  1. ESLint 是一个代码规范检查工具
  2. 它定义了很多特定的规则, 一旦你的代码违背了某一规则, eslint会作出非常有用的提示
  3. 官网: http://eslint.org/
  4. 基本已替代以前的 JSLint

ESLint 提供以下支持

  1. ES
  2. JSX
  3. style 检查
  4. 自定义错误和提示

ESLint 提供以下几种校验

  1. 语法错误校验
  2. 不重要或丢失的标点符号,如分号
  3. 没法运行到的代码块(使用过 WebStorm 的童鞋应该了解)
  4. 未被使用的参数提醒
  5. 确保样式的统一规则,如 sass 或者 less
  6. 检查变量的命名

规则的错误等级有三种

  1. 0:关闭规则。
  2. 1:打开规则,并且作为一个警告(信息打印黄色字体)
  3. 2:打开规则,并且作为一个错误(信息打印红色字体)

相关配置文件

  1. .eslintrc.js : 全局规则配置文件
1
2
3
'rules': {
'no-new': 1
}
  1. 在 js/vue 文件中修改局部规则
1
2
3
4
/* eslint-disable no-new */
new Vue({
el: 'body', components: { App }
})
  1. .eslintignore: 指令检查忽略的文件
    *.js

*.vue

组件定义与使用

vue 文件的组成(3 个部分)

  1. 模板页面
1
2
3
<template>
页面模板
</template>
  1. JS 模块对象
1
2
3
4
5
6
7
8
<script>
export default {
data() {return {}},
methods: {},
computed: {},
components: {}
}
</script>
  1. 样式
1
2
3
<style>
样式定义
</style>

基本使用

  1. 引入组件
  2. 映射成标签
  3. 使用组件标签
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<HelloWorld></HelloWorld>
<hello-world></hello-world>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
components: {
HelloWorld
}
}
</script>

关于标签名与标签属性名书写问题

  1. 写法一: 一模一样
  2. 写法二: 大写变小写, 并用 - 连接

组件间通信

组件间通信基本原则

  1. 不要在子组件中直接修改父组件的状态数据
  2. 数据在哪, 更新数据的行为(函数)就应该定义在哪

vue 组件间通信方式

  1. props
  2. vue 的自定义事件
  3. 消息订阅与发布(如: pubsub 库)
  4. slot
  5. vuex(后面单独讲)

组件间通信 1: props

使用组件标签时

1
<my-component name='tom' :age='3' :set-name='setName'></my-component>

定义 MyComponent 时

  1. 在组件内声明所有的 props
  2. 方式一: 只指定名称
1
props: ['name', 'age', 'setName']
  1. 方式二: 指定名称和类型
1
2
3
4
5
props: {
name: String,
age: Number,
setNmae: Function
}
  1. 方式三: 指定名称/类型/必要性/默认值
1
2
3
props: {
name: {type: String, required: true, default:xxx},
}

注意

  1. 此方式用于父组件向子组件传递数据
  2. 所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
  3. 问题:
    a. 如果需要向非子后代传递数据必须多层逐层传递
    b. 兄弟组件间也不能直接 props 通信, 必须借助父组件才可以

组件间通信 2: vue 自定义事件

绑定事件监听

1
2
3
4
5
6
// 方式一: 通过 v-on 绑定
@delete_todo="deleteTodo"
// 方式二: 通过$on()
this.$refs.xxx.$on('delete_todo', function (todo) {
this.deleteTodo(todo)
})

触发事件

1
2
// 触发事件(只能在父组件中接收)
this.$emit(eventName, data)

注意:

  1. 此方式只用于子组件向父组件发送消息(数据)
  2. 问题: 隔代组件或兄弟组件间通信此种方式不合适

组件间通信 3: 消息订阅与发布(PubSubJS 库)

订阅消息

1
PubSub.subscribe('msg', function(msg, data){})

发布消息

1
PubSub.publish('msg', data)

注意

  1. 优点: 此方式可实现任意关系组件间通信(数据)

事件的 2 个重要操作(总结)

  1. 绑定事件监听 (订阅消息)
    目标: 标签元素 <button>
    事件名(类型): click/focus
    回调函数: function(event){}
  2. 触发事件 (发布消息)
    DOM 事件: 用户在浏览器上对应的界面上做对应的操作
    自定义: 编码手动触发

组件间通信 4: slot

理解
此方式用于父组件向子组件传递标签数据

子组件: Child.vue

1
2
3
4
5
6
7
<template>
<div>
<slot name="xxx">不确定的标签结构 1</slot>
<div>组件确定的标签结构</div>
<slot name="yyy">不确定的标签结构 2</slot>
</div>
</template>

父组件: Parent.vue

1
2
3
4
<child>
<div slot="xxx">xxx 对应的标签结构</div>
<div slot="yyy">yyyy 对应的标签结构</div>
</child>

vue-ajax

vue 项目中常用的 2 个 ajax 库

3.1.1. vue-resource
vue 插件, 非官方库, vue1.x 使用广泛

3.1.2. axios
通用的 ajax 请求库, 官方推荐, vue2.x 使用广泛

3.2. vue-resource 的使用
3.2.1. 在线文档
https://github.com/pagekit/vue-resource/blob/develop/docs/http.md

3.2.2. 下载

1
npm install vue-resource --save

编码

1
2
3
4
5
6
7
8
9
10
11
// 引入模块
import VueResource from 'vue-resource' // 使用插件
Vue.use(VueResource)
// 通过 vue/组件对象发送 ajax 请求
this.$http.get('/someUrl').then((response) => {
// success callback
console.log(response.data) //返回结果数据
}, (response) => {
// error callback
console.log(response.statusText) //错误信息
})

axios 的使用

在线文档
https://github.com/pagekit/vue-resource/blob/develop/docs/http.md

下载:

1
npm install axios --save

编码

1
2
3
4
5
6
7
8
9
// 引入模块
import axios from 'axios' // 发送 ajax 请求
axios.get(url)
.then(response => {
console.log(response.data) // 得到返回结果数据
})
.catch(error => {
console.log(error.message)
})

测试接口
接口 1: https://api.github.com/search/repositories?q=v&sort=stars
接口 2: https://api.github.com/search/users?q=aa

vue UI 组件库

常用

  1. Mint UI:
    a. 主页: http://mint-ui.github.io/#!/zh-cn
    b. 说明: 饿了么开源的基于 vue 的移动端 UI 组件库
  2. Elment
    a. 主页: http://element-cn.eleme.io/#/zh-CN
    b. 说明: 饿了么开源的基于 vue 的 PC 端 UI 组件库

使用 Mint UI

下载:

1
npm install --save mint-ui

实现按需打包

  1. 下载
1
npm install --save-dev babel-plugin-component
  1. 修改 babel 配置
1
2
3
4
5
6
"plugins": ["transform-runtime",["component", [
{
"libraryName": "mint-ui",
"style": true
}
]]]

mint-ui 组件分类

  1. 标签组件
  2. 非标签组件

使用 mint-ui 的组件

  1. index.html
1
2
3
4
5
6
7
8
9
10
11
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1,
minimum-scale=1, user-scalable=no" />
<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() { FastClick.attach(document.body);}, false);
}
if(!window.Promise) {
document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
}
</script>
  1. main.js
1
2
import {Button} from 'mint-ui'
Vue.component(Button.name, Button)
  1. App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<mt-button @click="handleClick" type="primary" style="width: 100%">Test</mt-button>
</template>
<script>
import {Toast} from 'mint-ui'
export default {
methods: {
handleClick () {
Toast('点击了测试');
}
}
}
</script>

vue-router

理解

说明

  1. 官方提供的用来实现 SPA 的 vue 插件
  2. github: https://github.com/vuejs/vue-router
  3. 中文文档: http://router.vuejs.org/zh-cn/
  4. 下载: npm install vue-router --save

目录

1
2
3
4
5
6
7
8
9
10
11
vue_router
├── src
│ ├── pages
│ │ ├── Home.vue
│ │ └── ...
│ ├── router
│ │ └── index.js
│ ├── App.vue
│ └── Compiler.js
|
└── package.json

相关 API 说明

  1. VueRouter(): 用于创建路由器的构建函数
1
2
3
4
5
6
new VueRouter({
// 多个配置项
routes:[
...
]
})
  1. 路由配置
1
2
3
4
5
6
7
8
9
routes: [
{ // 一般路由
path: '/about',
component: About
},{ // 自动跳转路由
path: '/',
redirect: '/about'
}
]
  1. 注册路由器
1
2
3
4
import router from './router'
new Vue({
router
})
  1. 使用路由组件标签
  1. <router-link> : 用来生成路由链接
1
<router-link to="/xxx">Go to XXX</router-link>
  1. router-view: 用来显示当前路由组件界面

    1
    <router-view></router-view>

基本路由

路由组件
Home.vue
About.vue

应用组件: App.vue

1
2
3
4
5
6
7
<div>
<!--路由链接-->
<router-link to="/about">About</router-link>
<router-link to="/home">Home</router-link>
<!--用于渲染当前路由组件-->
<router-view></router-view>
</div>

路由器模块: src/router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default new VueRouter({
routes: [
{
path: '/',
redirect: '/about'
},{
path: '/about',
component: About
},{
path: '/home',
component: Home
}
]
})

注册路由器: main.js

1
2
3
4
5
6
7
import Vue from 'vue' 
import router from './router' // 创建 vue 配置路由器
new Vue({
el: '#app',
router,
render: h => h(app)
})

优化路由器配置

1
linkActiveClass: 'active', // 指定选中的路由链接的 class

总结: 编写使用路由的 3 步

  1. 定义路由组件

  2. 注册路由

  3. 使用路由

1
2
<router-link>
<router-view>

嵌套路由

子路由组件
News.vue
Message.vue

配置嵌套路由: router.js

1
2
3
4
5
6
7
8
9
10
11
path: '/home',
component: home,
children: [
{
path: 'news',
component: News
},{
path: 'message',
component: Message
}
]

路由链接: Home.vue

1
2
3
<router-link to="/home/news">News</router-link>
<router-link to="/home/message">Message</router-link>
<router-view></router-view>

向路由组件传递数据

方式 1: 路由路径携带参数(param/query)

  1. 配置路由
1
2
3
4
5
6
children: [
{
path: 'mdetail/:id',
component: MessageDetail
}
]
  1. 路由路径
1
<router-link :to="'/home/message/mdetail/'+m.id">{{m.title}}</router-link>
  1. 路由组件中读取请求参数
    this.$route.params.id

方式 2: 属性携带数据

1
<router-view :msg="msg"></router-view>

缓存路由组件对象

理解

  1. 默认情况下, 被切换的路由组件对象会死亡释放, 再次回来时是重新创建的
  2. 如果可以缓存路由组件对象, 可以提高用户体验

编码实现

1
2
3
<keep-alive>
<router-view></router-view>
</keep-alive>

编程式路由导航

相关 API

  1. this.$router.push(path) : 相当于点击路由链接(可以返回到当前路由界面)
  2. this.$router.replace(path) : 用新路由替换当前路由(不可以返回到当前路由界面)
  3. this.$router.back() : 请求(返回)上一个记录路由
  4. this.$router.go(-1) : 请求(返回)上一个记录路由
  5. this.$router.go(1) : 请求下一个记录路由

vue 源码分析

vue源码解析代码:https://github.com/cheungww/Vue_practice/tree/master/VueSource

说明

  1. 分析 vue 作为一个 MVVM 框架的基本实现原理
    数据代理
    模板解析
    数据绑定
  2. 不直接看 vue.js 的源码
  3. 剖析 github 上某基友仿 vue 实现的 mvvm 库
  4. 地址: https://github.com/DMQ/mvvm

准备知识

  1. [].slice.call(lis): 将伪数组转换为真数组

  2. node.nodeType: 得到节点类型

  3. Object.defineProperty(obj, propName, {}): 给对象添加/修改属性(指定描述符)
    configurable: true/false 是否可以重新 define
    enumerable: true/false 是否可以枚举(for…in / keys())
    value: 指定初始值
    writable: true/false value 是否可以修改
    get: 回调函数, 用来得到当前属性值
    set: 回调函数, 用来监视当前属性值的变化

  4. Object.keys(obj): 得到对象自身可枚举的属性名的数组

  5. DocumentFragment: 文档碎片(高效批量更新多个节点)

  6. obj.hasOwnProperty(prop): 判断 prop 是否是 obj 自身的属性

数据代理

  1. 数据代理: 通过一个对象代理对另一个对象(在前一个对象内部)中属性的操作(读/写)

  2. vue 数据代理: 通过 vm 对象来代理 data 对象中所有属性的操作

  3. 好处: 更方便的操作 data 中的数据

  4. 基本实现流程

  1. 通过 Object.defineProperty()给 vm 添加与 data 对象的属性对应的属性描述符
  2. 所有添加的属性都包含 getter/setter
  3. getter/setter 内部去操作 data 中对应的属性数据

数据代理_vue.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>02_数据代理_vue</title>
</head>
<body>

<div id="test"></div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
const vm = new Vue({
el: "#test",
data: {
name: '张三'
}
})
console.log(vm.name) // 读取的是data中的name, vm代理对data的读操作
vm.name = '李四' // 数据保存到data中的name上, vm代理对data的写操作
console.log(vm.name, vm._data.name)


</script>
</body>
</html>

数据代理_mvvm.html(模拟vue的数据代理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>02_数据代理</title>
</head>
<body>

<!--
1. vue数据代理: data对象的所有属性的操作(读/写)由vm对象来代理操作
2. 好处: 通过vm对象就可以方便的操作data中的数据
3. 实现:
1). 通过Object.defineProperty(vm, key, {})给vm添加与data对象的属性对应的属性
2). 所有添加的属性都包含get/set方法
3). 在get/set方法中去操作data中对应的属性
-->
<div id="test"></div>

<script type="text/javascript" src="js/mvvm/compile.js"></script>
<script type="text/javascript" src="js/mvvm/mvvm.js"></script>
<script type="text/javascript" src="js/mvvm/observer.js"></script>
<script type="text/javascript" src="js/mvvm/watcher.js"></script>
<script type="text/javascript">
const vm = new MVVM({
el: "#test",
data: {
name: '张三2'
}
})
console.log(vm.name) // 读取的是data中的name, vm代理对data的读操作
vm.name = '李四2' // 数据保存到data中的name上, vm代理对data的写操作
console.log(vm.name, vm._data.name)
</script>
</body>
</html>

mvvm.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
相关于Vue的构造函数
*/
function MVVM(options) {
// 将选项对象保存到vm
this.$options = options;
// 将data对象保存到vm和datq变量中
var data = this._data = this.$options.data;
//将vm保存在me变量中
var me = this;
// 遍历data中所有属性
Object.keys(data).forEach(function (key) { // 属性名: name
// 对指定属性实现代理
me._proxy(key);
});

// 对data进行监视
observe(data, this);

// 创建一个用来编译模板的compile对象
this.$compile = new Compile(options.el || document.body, this)
}

MVVM.prototype = {
$watch: function (key, cb, options) {
new Watcher(this, key, cb);
},

// 对指定属性实现代理
_proxy: function (key) {
// 保存vm
var me = this;
// 给vm添加指定属性名的属性(使用属性描述)
Object.defineProperty(me, key, {
configurable: false, // 不能再重新定义
enumerable: true, // 可以枚举
// 当通过vm.name读取属性值时自动调用
get: function proxyGetter() {
// 读取data中对应属性值返回(实现代理读操作)
return me._data[key];
},
// 当通过vm.name = 'xxx'时自动调用
set: function proxySetter(newVal) {
// 将最新的值保存到data中对应的属性上(实现代理写操作)
me._data[key] = newVal;
}
});
}
};

模板解析

模板解析的基本流程

  1. 将 el 的所有子节点取出, 添加到一个新建的文档 fragment 对象中

  2. 对 fragment 中的所有层次子节点递归进行编译解析处理

  • 对大括号表达式文本节点进行解析
  • 对元素节点的指令属性进行解析
  • 事件指令解析
  • 一般指令解析
  1. 将解析后的 fragment 添加到 el 中显示

模板解析(1): 大括号表达式解析

  1. 根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1 name
  2. 从 data 中取出表达式对应的属性值
  3. 将属性值设置为文本节点的 textContent

模板解析(2): 事件指令解析

  1. 从指令名中取出事件名
  2. 根据指令的值(表达式)从 methods 中得到对应的事件处理函数对象
  3. 给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
  4. 指令解析完后, 移除此指令属性

模板解析(3): 一般指令解析

  1. 得到指令名和指令值(表达式) text/html/class msg/myClass
  2. 从 data 中根据表达式得到对应的值
  3. 根据指令名确定需要操作元素节点的什么属性
  • v-text—textContent 属性
  • v-html—innerHTML 属性
  • v-class–className 属性
  1. 将得到的表达式的值设置到对应的属性上
  2. 移除元素的指令属性

模板解析_表达式_vue.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>03_模板解析_表达式_vue</title>
</head>
<body>

<div id="test">
<p>{{name}}</p>
</div>


<script type="text/javascript" src="js/mvvm/compile.js"></script>
<script type="text/javascript" src="js/mvvm/mvvm.js"></script>
<script type="text/javascript" src="js/mvvm/observer.js"></script>
<script type="text/javascript" src="js/mvvm/watcher.js"></script>
<script type="text/javascript">
new MVVM({
el: '#test',
data: {
name: 'SADAMU'
}
})
</script>
</body>
</html>

compile.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
function Compile(el, vm) {
// 保存vm
this.$vm = vm;
// 保存el元素
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
// 如果el元素存在
if (this.$el) {
// 1. 取出el中所有子节点, 封装在一个framgment对象中
this.$fragment = this.node2Fragment(this.$el);
// 2. 编译fragment中所有层次子节点
this.init();
// 3. 将fragment添加到el中
this.$el.appendChild(this.$fragment);
}
}

Compile.prototype = {
node2Fragment: function (el) {
var fragment = document.createDocumentFragment(),
child;

// 将原生节点拷贝到fragment
while (child = el.firstChild) {
fragment.appendChild(child);
}

return fragment;
},

init: function () {
// 编译fragment
this.compileElement(this.$fragment);
},

compileElement: function (el) {
// 得到所有子节点
var childNodes = el.childNodes,
// 保存compile对象
me = this;
// 遍历所有子节点
[].slice.call(childNodes).forEach(function (node) {
// 得到节点的文本内容
var text = node.textContent;
// 正则对象(匹配大括号表达式)
var reg = /\{\{(.*)\}\}/; // {{name}}
// 如果是元素节点
if (me.isElementNode(node)) {
// 编译元素节点的指令属性
me.compile(node);
// 如果是一个大括号表达式格式的文本节点
} else if (me.isTextNode(node) && reg.test(text)) {
// 编译大括号表达式格式的文本节点
me.compileText(node, RegExp.$1); // RegExp.$1: 表达式 name
}
// 如果子节点还有子节点
if (node.childNodes && node.childNodes.length) {
// 递归调用实现所有层次节点的编译
me.compileElement(node);
}
});
},

compile: function (node) {
// 得到所有标签属性节点
var nodeAttrs = node.attributes,
me = this;
// 遍历所有属性
[].slice.call(nodeAttrs).forEach(function (attr) {
// 得到属性名: v-on:click
var attrName = attr.name;
// 判断是否是指令属性
if (me.isDirective(attrName)) {
// 得到表达式(属性值): test
var exp = attr.value;
// 得到指令名: on:click
var dir = attrName.substring(2);
// 事件指令
if (me.isEventDirective(dir)) {
// 解析事件指令
compileUtil.eventHandler(node, me.$vm, exp, dir);
// 普通指令
} else {
// 解析普通指令
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
}

// 移除指令属性
node.removeAttribute(attrName);
}
});
},

compileText: function (node, exp) {
// 调用编译工具对象解析
compileUtil.text(node, this.$vm, exp);
},

isDirective: function (attr) {
return attr.indexOf('v-') == 0;
},

isEventDirective: function (dir) {
return dir.indexOf('on') === 0;
},

isElementNode: function (node) {
return node.nodeType == 1;
},

isTextNode: function (node) {
return node.nodeType == 3;
}
};

// 指令处理集合
var compileUtil = {
// 解析: v-text/{{}}
text: function (node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
// 解析: v-html
html: function (node, vm, exp) {
this.bind(node, vm, exp, 'html');
},

// 解析: v-model
model: function (node, vm, exp) {
this.bind(node, vm, exp, 'model');

var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function (e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}

me._setVMVal(vm, exp, newValue);
val = newValue;
});
},

// 解析: v-class
class: function (node, vm, exp) {
this.bind(node, vm, exp, 'class');
},

// 真正用于解析指令的方法
bind: function (node, vm, exp, dir) {
/*实现初始化显示*/
// 根据指令名(text)得到对应的更新节点函数
var updaterFn = updater[dir + 'Updater'];
// 如果存在调用来更新节点
updaterFn && updaterFn(node, this._getVMVal(vm, exp));

// 创建表达式对应的watcher对象
new Watcher(vm, exp, function (value, oldValue) {/*更新界面*/
// 当对应的属性值发生了变化时, 自动调用, 更新对应的节点
updaterFn && updaterFn(node, value, oldValue);
});
},

// 事件处理
eventHandler: function (node, vm, exp, dir) {
// 得到事件名/类型: click
var eventType = dir.split(':')[1],
// 根据表达式得到事件处理函数(从methods中): test(){}
fn = vm.$options.methods && vm.$options.methods[exp];
// 如果都存在
if (eventType && fn) {
// 绑定指定事件名和回调函数的DOM事件监听, 将回调函数中的this强制绑定为vm
node.addEventListener(eventType, fn.bind(vm), false);
}
},

// 得到表达式对应的value
_getVMVal: function (vm, exp) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function (k) {
val = val[k];
});
return val;
},

_setVMVal: function (vm, exp, value) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function (k, i) {
// 非最后一个key,更新val的值
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = value;
}
});
}
};

// 包含多个用于更新节点方法的对象
var updater = {
// 更新节点的textContent
textUpdater: function (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},

// 更新节点的innerHTML
htmlUpdater: function (node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value;
},

// 更新节点的className
classUpdater: function (node, value, oldValue) {
// 静态class属性的值
var className = node.className;
/* className = className.replace(oldValue, '').replace(/\s$/, '');

var space = className && String(value) ? ' ' : '';

node.className = className + space + value; */
// 将静态class 属性的值与动态class值进行合并后设置为新的className属性值
node.className = className + (className ? ' ' : '') + value;
},

// 更新节点的value
modelUpdater: function (node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
};

数据绑定

数据绑定

一旦更新了 data 中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新

数据劫持

  1. 数据劫持是 vue 中用来实现数据绑定的一种技术
  2. 基本思想: 通过 defineProperty()来监视 data 中所有属性(任意层次)数据的变化, 一旦变化就去更新界面

例如,this.xxx = 3 (这里的this指vm,改变vm的xxx属性值),那么就会调用this的setter函数对data.xxx 的属性值进行改变,data.xxx改变后,就会触发data的setter函数,然后data的setter函数应该是绑定了data.xxx的属性值与界面上的,所有当触发data的setter时,则更新了界面上相对应的

四个重要对象

  1. Observer
    a. 用来对 data 所有属性数据进行劫持的构造函数
    b. 给 data 中所有属性重新定义属性描述(get/set)
    c. 为 data 中的每个属性创建对应的 dep 对象
  2. Dep(Depend)
    a. data 中的每个属性(所有层次)都对应一个 dep 对象
    b. 创建的时机:
  • 在初始化 define data 中各个属性时创建对应的 dep 对象

  • 在 data 中的某个属性值被设置为新的对象时

c. 对象的结构

1
2
3
4
{
id, // 每个 dep 都有一个唯一的 id
subs //包含 n 个对应 watcher 的数组(subscribes 的简写)
}

d. subs 属性说明

  • 当 watcher 被创建时, 内部将当前 watcher 对象添加到对应的 dep 对象的 subs 中
  • 当此 data 属性的值发生改变时, subs 中所有的 watcher 都会收到更新的通知,从而最终更新对应的界面
  1. Compiler
    a. 用来解析模板页面的对象的构造函数(一个实例)
    b. 利用 compile 对象解析模板页面
    c. 每解析一个表达式(非事件指令)都会创建一个对应的 watcher 对象, 并建立 watcher与 dep 的关系
    d. complie 与 watcher 关系: 一对多的关系
  2. Watcher
    a. 模板中每个非事件指令或表达式都对应一个 watcher 对象
    b. 监视当前表达式数据的变化
    c. 创建的时机: 在初始化编译模板时
    d. 对象的组成
1
2
3
4
5
6
7
8
{
vm, //vm 对象
exp, //对应指令的表达式
cb, //当表达式所对应的数据发生改变的回调函数
value, //表达式当前的值
depIds //表达式中各级属性所对应的 dep 对象的集合对象
//属性名为 dep 的 id, 属性值为 dep
}
  1. 总结: dep 与 watcher 的关系: 多对多
    a. data 中的一个属性对应一个 dep, 一个 dep 中可能包含多个 watcher(模板中有几个表达式使用到了同一个属性)
    b. 模板中一个非事件表达式对应一个 watcher, 一个 watcher 中可能包含多个 dep(表达式是多层: a.b)
    c. 数据绑定使用到 2 个核心技术
  • defineProperty()
  • 消息订阅与发布

MVVM 原理图分析

1581413144866

双向数据绑定

  1. 双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
  2. 双向数据绑定的实现流程:
    a. 在解析 v-model 指令时, 给当前元素添加 input 监听
    b. 当 input 的 value 发生改变时, 将最新的值赋值给当前表达式所对应的 data 属性

vuex

vuex 理解

vuex 是什么

  1. github 站点: https://github.com/vuejs/vuex
  2. 在线文档: https://vuex.vuejs.org/zh-cn/
  3. 简单来说: 对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)

状态自管理应用

  1. state: 驱动应用的数据源

  2. view: 以声明方式将 state 映射到视图

  3. actions: 响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的方法)

1581430491905

vuex

多组件共享状态的问题

  1. 多个视图依赖于同一状态
  2. 来自不同视图的行为需要变更同一状态
  3. 以前的解决办法
    a. 将数据以及操作数据的行为都定义在父组件
    b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
  4. vuex 就是用来解决这个问题的

vuex 核心概念和 API

state

  1. vuex 管理的状态对象

  2. 它应该是唯一的

1
2
3
const state = {
xxx: initValue
}

mutations

  1. 包含多个直接更新 state 的方法(回调函数)的对象
  2. 谁来触发: action 中的 commit(‘mutation 名称’)
  3. 只能包含同步的代码, 不能写异步代码
1
2
3
4
5
const mutations = {
yyy (state, {data1}) {
// 更新 state 的某个属性
}
}

actions

  1. 包含多个事件回调函数的对象

  2. 通过执行: commit()来触发 mutation 的调用, 间接更新 state

  3. 谁来触发: 组件中: $store.dispatch('action 名称', data1) // 'zzz'

  4. 可以包含异步代码(定时器, ajax)

1
2
3
4
5
const actions = {
zzz ({commit, state}, data1) {
commit('yyy', {data1})
}
}

getters

  1. 包含多个计算属性(get)的对象
  2. 谁来读取: 组件中: $store.getters.xxx
1
2
3
4
5
const getters = {
mmm (state) {
return ...
}
}

modules

  1. 包含多个 module
  2. 一个 module 是一个 store 的配置对象
  3. 与一个组件(包含有共享数据)对应

向外暴露 store 对象
export default new Vuex.Store({
state, mutations, actions, getters
})

组件中

1
2
3
4
5
6
7
8
9
import {mapState, mapGetters, mapActions} from 'vuex'
export default {
computed: {
...mapState(['xxx']),
...mapGetters(['mmm'])
}
methods: mapActions(['zzz'])
}
{{xxx}} {{mmm}} @click="zzz(data)"

映射 store

1
2
3
4
import store from './store'
new Vue({
store
})

store 对象

  1. 所有用 vuex 管理的组件中都多了一个属性$store, 它就是一个 store 对象
  2. 属性:
    state: 注册的 state 对象
    getters: 注册的 getters 对象
  3. 方法:
    dispatch(actionName, data): 分发调用 action

demo1: 计数器

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/*
vuex最核心的管理对象store
*/
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

/*
相当于data对象的状态对象
*/
const state = {
count: 0 // 指定初始化数据
}

/*
包含了n个直接更新状态的方法的对象
mutations 对象
包含个方法: 能直接更新 state 一个方法就是一个 mutation
mutation 只能包含更新 state 的同步代码, 也不会有逻辑
mutation 由 action 触发调用: commit('mutationName')
*/
const mutations = {
INCREMENT(state) {
state.count++
},
DECREMENT(state) {
state.count--
}
}

/*
包含了n个间接更新状态的方法的对象
actions 对象
包含个方法: 触发 mutation 调用, 间接更新 state 一个方法就是一个 action
action 中可以有逻辑代码和异步代码
action 由组件来触发调用: this.$store.dispatch('actionName')
*/
const actions = {
increment({ commit }) {
// 提交一个mutation请求
commit('INCREMENT')
},
decrement({ commit }) {
// 提交一个mutation请求
commit('DECREMENT')
},
incrementIfOdd({ commit, state }) {
if (state.count % 2 === 1) {
// 提交一个mutation请求
commit('INCREMENT')
}
},
incrementAsync({ commit }) {
setTimeout(() => {
// 提交一个mutation请求
commit('INCREMENT')
}, 1000)
},
}

/*
getters 对象
包含多个getter计算属性的对象
*/
const getters = {
evenOrOdd(state) { // 当读取属性值时自动调用并返回属性值
return state.count % 2 === 0 ? '偶数' : '奇数'
}
}

// 向外暴露 store 实例对象
export default new Vuex.Store({
state,
mutations,
actions,
getters
})

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
入口js
*/
import Vue from 'vue'
import Counter from './Counter.vue'
import store from './store'

/* new Vue({
el: '#app',
components: {
Counter
},
template: '<Counter/>',
store // 注册上vuex的store: 所有组件对象都多一个属性$store
}) */

new Vue({
el: '#app',
render: h => h(app),
store // 所有组件都多个一个属性: $store
})

/* new Vue({
el: '#app',
render: function (createElement) {
return createElement(App) // <App/>
},
store
})
*/

app.vue(未优化前)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<template>
<div>
<p>click {{count}} times, count is {{evenOrOdd}}</p>

<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>

<script>
export default {

mounted () {
console.log(this.$store)
},

computed: {
count () {
return this.$store.state.count
},
evenOrOdd () {
return this.$store.getters.evenOrOdd
}
},

methods: {
increment () {
this.$store.dispatch('increment')
},
decrement () {
this.$store.dispatch('decrement')
},
incrementIfOdd () {
this.$store.dispatch('incrementIfOdd')
},
incrementAsync () {
this.$store.dispatch('incrementAsync')
}
}
}
</script>

<style>

</style>

app2.vue(优化后)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div>
<p>click {{count}} times, count is {{evenOrOdd}}</p>

<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>

<script>
import {mapState, mapGetters, mapActions} from 'vuex'

export default {

mounted () {
console.log(this.$store)
},
computed: {
...mapState(['count']),
...mapGetters(['evenOrOdd'])
},
methods: {
...mapActions(['increment', 'decrement', 'incrementIfOdd', 'incrementAsync'])
}
}
</script>
<style>
</style>

demo2: todo list

store/mutation-types.js

1
2
3
4
5
6
7
/*
包含n个mutation名称常量
*/
export const ADD_TODO = 'add_todo' // 添加todo
export const DELETE_TODO = 'delete_todo' // 删除todo
export const SELECT_ALL_TODOS = 'select_all_todos' // 全选/全不选todos
export const DELETE_COMPLETE_TODOS = 'delete_complete_todos' // 删除所有选中的

store/mutations.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
包含n个用于直接更新状态的方法的对象模块
*/
import {ADD_TODO, DELETE_TODO, SELECT_ALL_TODOS, DELETE_COMPLETE_TODOS} from './mutation-types'

export default {
[ADD_TODO] (state, {todo}) { // 方法名不是ADD_TODO, 而是add_todo
state.todos.unshift(todo)
},
[DELETE_TODO] (state, {index}) {
state.todos.splice(index, 1)
},
[SELECT_ALL_TODOS] (state, {isCheck}) {
state.todos.forEach(todo => todo.complete = isCheck)
},

[DELETE_COMPLETE_TODOS] (state) {
state.todos = state.todos.filter(todo => !todo.complete)
}
}

store/actions.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
包含n个用于间接更新状态的方法的对象模块
*/
import {ADD_TODO, DELETE_TODO, SELECT_ALL_TODOS, DELETE_COMPLETE_TODOS} from './mutation-types'

export default {

addTodo ({commit}, todo) {
// 提交一个comutation请求
commit(ADD_TODO, {todo}) // 传递给mutation的是一个包含数据的对象
},

deleteTodo ({commit}, index) {
commit(DELETE_TODO, {index})
},

selectAll ({commit}, isCheck) {
commit(SELECT_ALL_TODOS, {isCheck})
},

deleteCompleteTodos ({commit}) {
commit(DELETE_COMPLETE_TODOS)
}
}

store/getters.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
包含n个基于state的getter计算属性方法的对象模块
*/
export default {

// 总数量
totalSize (state) {
return state.todos.length
},
// 完成的数量
completeSize (state) {
return state.todos.reduce((preTotal, todo) => preTotal + (todo.complete?1:0) ,0)
},

// 判断是否需要全选
isAllSelect (state, getters) {
return getters.completeSize===getters.totalSize && getters.completeSize>0
}
}

store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
vuex核心管理模块store对象
*/
import Vue from 'vue'
import Vuex from 'vuex'

import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'

Vue.use(Vuex)

export default new Vuex.Store({
state,
mutations,
actions,
getters
})

components/app.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<div class="todo-container">
<div class="todo-wrap">
<TodoHeader/>
<TodoList/>
<TodoFooter/>
</div>
</div>
</template>

<script>
import TodoHeader from './components/TodoHeader.vue'
import TodoList from './components/TodoList.vue'
import TodoFooter from './components/TodoFooter.vue'

export default {

components: {
TodoHeader,
TodoList,
TodoFooter
}
}
</script>

<style>
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>

components/todoHeader.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="inputTodo" @keyup.enter="add"/>
</div>
</template>

<script>
export default {
data () {
return {
inputTodo: '' // 不需要使用vuex管理(只有当前组件使用)
}
},
methods: {
add () {
// 得到输入的数据
const inputTodo = this.inputTodo.trim()
// 检查合法性
if(!inputTodo) {
alert('必须输入')
return
}
// 封装一个todo对象
const todo = {
title: inputTodo,
complete: false
}
// 添加到todos中显示
// this.addTodo(todo)
this.$store.dispatch('addTodo', todo)
// 清除输入
this.inputTodo = ''
}
}
}
</script>

<style>
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}

.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
</style>

components/TodoList.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
<ul class="todo-main">
<TodoItem v-for="(todo, index) in todos" :key="index"
:todo="todo" :index="index"/>
</ul>
</template>

<script>
import {mapState} from 'vuex'
import TodoItem from './TodoItem.vue'
import storageUtils from '../utils/storageUtils'

export default {

computed: {
...mapState(['todos'])
},

watch: {
todos: {
deep: true, // 深度监视
handler: storageUtils.saveTodos,
}
},

components: {
TodoItem
}
}
</script>

<style>
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}

.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
</style>

components/todoItem.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<template>
<li :style="{background: bgColor}" @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)">
<label>
<input type="checkbox" v-model="todo.complete"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" v-show="isShow" @click="deleteItem">删除</button>
</li>
</template>

<script>
export default {
props: {// 指定属性名和属性值的类型
todo: Object,
index: Number
},

data () {
return {
bgColor: 'white',
isShow: false
}
},

methods: {
handleEnter (isEnter) {
if(isEnter) { // 进入
this.bgColor = '#cccccc'
this.isShow = true
} else { // 离开
this.bgColor = '#ffffff'
this.isShow = false
}

},

deleteItem () {
// this.deleteTodo(this.index)
this.$store.dispatch('deleteTodo', this.index)
}
}
}
</script>

<style>
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}

li label {
float: left;
cursor: pointer;
}

li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}

li button {
float: right;
display: none;
margin-top: 3px;
}

li:before {
content: initial;
}

li:last-child {
border-bottom: none;
}
</style>

components/TodoFooter.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<template>
<div class="todo-footer">
<label>
<input type="checkbox" v-model="checkAll"/>
</label>
<span>
<span>已完成{{completeSize}}</span> / 全部{{totalSize}}
</span>
<button class="btn btn-danger" v-show="completeSize" @click="deleteAllCompleted">清除已完成任务</button>
</div>
</template>

<script>
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters(['totalSize', 'completeSize']),

checkAll: {
get () { // 决定是否勾选
return this.$store.getters.isAllSelect
},

set (value) {// 点击了全选checkbox value是当前checkbox的选中状态(true/false)
// this.selectAll(value)
this.$store.dispatch('selectAll', value)
}
},
},

methods: {
deleteAllCompleted () {
if(window.confirm('确定清除已完成的吗?')) {
// this.deleteCompleteTodos()
this.$store.dispatch('deleteCompleteTodos')
}
}
}
}
</script>

<style>
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}

.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}

.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}

.todo-footer button {
float: right;
margin-top: 5px;
}

</style>

util/storageUtil.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
向local中存储数据的工具模块
1. 向外暴露一个函数(功能)
只有一个功能需要暴露
2. 向外暴露一个对象(包含多个功能)
有多个功能需要暴露
*/
const TODOS_KEY = 'todos_key'
export default {
readTodos () {
return JSON.parse(localStorage.getItem(TODOS_KEY) || '[]')
},
saveTodos (todos) {
localStorage.setItem(TODOS_KEY, JSON.stringify(todos))
}
}

base.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
body {
background: #fff;
}

.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}

.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}

.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}

.btn:focus {
outline: none;
}

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
入口JS
*/
import Vue from 'vue'
import App from './App.vue'
import store from './store'

import './base.css'

// 创建vm
/* eslint-disable no-new */
new Vue({
el: '#app',
components: {App}, // 映射组件标签
template: '<App/>', // 指定需要渲染到页面的模板
store // 所有的组件对象都多了一个属性: $store(值就是store对象)
})

vuex 结构分析

vuex 结构分析

-------文章到此结束  感谢您的阅读-------