初探前端框架 Vue2 之三

大纲

监听器(watch)

监听器介绍

watch 属性可以监听一个值的变化,从而做出相应的反应(渲染)。

监听器使用

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">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<ul>
<li>西游记:价格 {{xyjPrice}},数量:
<input type="number" v-model="xyjNum">
</li>
<li>水浒传:价格 {{shzPrice}},数量:
<input type="number" v-model="shzNum">
</li>
<li>提示信息:{{msg}}</li>
</ul>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>

<script>
let app = new Vue({
el: "#app",
data: {
xyjPrice: 56,
shzPrice: 47,
xyjNum: 1,
shzNum: 1,
msg: ''
},
watch: {
// 监听 xyjNum 值的变化
xyjNum(newVal, oldVal) {
if (newVal >= 3) {
this.msg = '库存不足';
this.xyjNum = 3;
} else {
this.msg = '';
}
}
}
});
</script>
</body>
</html>

代码运行效果

上述 HTML 代码的运行效果如下,当输入的西游记下单数量大于等于 3 时,页面会显示 库存不足 的提示信息。

过滤器(filters)

过滤器介绍

过滤器不会改变真正的 data,而只是改变渲染的结果,并返回过滤后的内容。在很多不同的业务场景下,过滤器都是有用的,比如尽可能保持 API 响应结果的干净,并在前端处理数据的格式。

提示

  • 过滤器常用来处理文本格式化的操作
  • 过滤器可以用在两个地方:双花括号插值 {{ }}v-bind 指令中

过滤器使用

局部过滤器使用

局部过滤器注册在当前的 Vue 实例中,只有当前 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<table border="1px" cellspacing="0" width="200px">
<tr align="center">
<th>ID</th>
<th>姓名</th>
<th>性别</th>
</tr>
<tr align="center" v-for="user in userList">
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<!-- 使用性别过滤器 -->
<td>{{user.gender | genderFilters}}</td>
</tr>
</table>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>

<script>
let app = new Vue({
el: "#app",
data: {
userList: [
{ id: 1, name: 'Jack', gender: 1 },
{ id: 2, name: 'Amy', gender: 0 }
]
},
filters: {
// 注册性别过滤器
genderFilters(val) {
if (val === 1) {
return "男";
}
else {
return "女";
}
}
}
});
</script>
</body>
</html>

代码运行效果

上述 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body style="margin: 100px;">
<div id="app">
<table border="1px" cellspacing="0" width="200px">
<tr align="center">
<th>ID</th>
<th>姓名</th>
<th>性别</th>
</tr>
<tr align="center" v-for="user in userList">
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<!-- 使用性别过滤器 -->
<td>{{user.gender | genderFilters}}</td>
</tr>
</table>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>

<script>

// 在创建 Vue 实例之前注册全局过滤器
Vue.filter('genderFilters', function (val) {
if (val === 1) {
return "男";
}
else {
return "女";
}
});

let app = new Vue({
el: "#app",
data: {
userList: [
{ id: 1, name: 'Jack', gender: 1 },
{ id: 2, name: 'Amy', gender: 0 }
]
}
});
</script>
</body>
</html>

代码运行效果

上述 HTML 代码的运行效果如下,当使用全局定义的性别过滤器后,页面渲染后会显示过滤后得到的性别。

计算属性(computed)

计算属性介绍

若某些渲染结果是基于已有数据实时计算出来的,那么可以利用 Vue 的计算属性(computed)来实现。

计算属性使用

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<ul>
<li>西游记:价格 {{xyjPrice}},数量:
<input type="number" v-model="xyjNum">
</li>
<li>水浒传:价格 {{shzPrice}},数量:
<input type="number" v-model="shzNum">
</li>
<li>总价:{{totalPrice}}</li>
</ul>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>

<script>
let app = new Vue({
el: "#app",
data: {
xyjPrice: 56,
shzPrice: 47,
xyjNum: 1,
shzNum: 1
},
computed: {
// 实时计算 totalPrice
totalPrice() {
return this.xyjPrice * this.xyjNum + this.shzPrice * this.shzNum;
}
}
});
</script>
</body>
</html>

代码运行效果

上述 HTML 代码的运行效果如下,只要 totalPrice 依赖的属性发生了变化,就会重新计算 totalPrice 的值。

组件化

在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分,例如可能会有相同的头部导航。但是如果每个页面都独自开发,这无疑增加了开发的成本。所以一般会把页面的不同部分拆分成独立的组件,然后在不同页面就可以共享这些组件,避免重复开发。在 Vue 里,所有的 Vue 实例都是组件,通常一个应用会以一棵嵌套的组件树的形式来组织(如下图)。例如,可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

全局组件

通过 Vue 的 component() 函数可以定义一个全局组件,component() 函数的第一个参数是组件名称,第二个参数是组件的参数。

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用已定义的全局组件 -->
<counter></counter>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>

<script>
// 在创建 Vue 实例之前,全局注册一个组件
Vue.component("counter", {
template: `<button v-on:click="count++">点击了 {{count}} 次</button>`,
data() {
return {
count: 1
}
}
});

let app = new Vue({
el: "#app"
});
</script>
</body>
</html>

代码运行效果

上述 HTML 代码的运行效果如下,每点击一次按钮,都会记录显示点击的总次数。

  • 组件其实也是一个 Vue 实例,因此它在定义时也会接收 datamethods、生命周期函数等
  • 与普通 Vue 实例不同的是,组件不会与页面的元素绑定,否则就无法复用了,因此没有 el 属性
  • 由于组件渲染需要 HTML 模板,所以增加了 template 属性,值就是 HTML 模板的内容
  • 全局组件定义完成后,任何 Vue 实例都可以直接在 HTML 中通过组件名称来使用组件
  • data 必须是一个函数,不再是一个对象,因此每个实例可以维护一份被返回对象的独立的拷贝。否则重复引用同一个组件时,数据会互相影响

局部组件

通过 Vue 的 components 属性可以定义一个局部组件,components 就是当前 Vue 实例子组件的集合。特别注意,局部组件只能在当前 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用已定义的局部组件 -->
<counter></counter>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>

<script>
let app = new Vue({
el: "#app",
components: {
// 注册局部组件
'counter': {
template: `<button v-on:click="count++">点击了 {{count}} 次</button>`,
data() {
return {
count: 1
}
}
}
}
});
</script>
</body>
</html>

代码运行效果

上述 HTML 代码的运行效果如下,每点击一次按钮,都会记录显示点击的总次数。

组件复用

定义好全局组件或局部组件后,在同一个 Vue 实例(页面)中可以任意重复使用多次。

特别注意

注册全局组件或局部组件时,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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 重复使用已定义的全局组件 -->
<counter></counter>
<counter></counter>
<counter></counter>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>

<script>
// 在创建 Vue 实例之前,全局注册一个组件
Vue.component("counter", {
template: `<button v-on:click="count++">点击了 {{count}} 次</button>`,
data() {
return {
count: 1
}
}
});

let app = new Vue({
el: "#app"
});
</script>
</body>
</html>

生命周期和钩子函数

每个 Vue 实例在被创建时都要经过一系列的初始化过程:创建实例,装载模板,渲染模板等等。Vue 为生命周期中的每个状态都设置了钩子函数(监听函数)。每当 Vue 实例处于不同的生命周期时,对应的钩子函数就会被触发调用。

生命周期

生命周期图示

下图展示了 Vue 实例的生命周期。开发者不需要立马弄明白所有的东西,不过随着不断学习和使用,它的参考价值会越来越高。

钩子函数

钩子函数介绍

  • beforeCreated:在使用 Vue 时都要进行实例化,因此该函数就是在 Vue 实例化时调用,也可以将它理解为初始化函数比较方便一点,在 Vue 1.0 版本时,这个函数的名字就是 init
  • created:在创建 Vue 实例之后进行调用
  • beforeMount:页面加载完成,没有渲染,例如此时页面还是会显示类似 {{name}} 的内容
  • mounted:可以将它理解为原生 JS 中的 window.onload=function(){},或许也可以理解为 JQuery 中的 $(document).ready(function(){}),它就是在 DOM 文档渲染完毕之后将要执行的函数,该函数在 Vue 1.0 版本中名字为 compiled,此时页面中的 {{name}} 已被渲染成 张三
  • beforeDestroy:该函数将在销毁实例前进行调用
  • destroyed:该函数将在销毁实例后进行调用
  • beforeUpdate:该函数将在组件更新之前调用
  • updated:该函数将在组件更新之后调用

钩子函数使用

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<span id="num">{{num}}</span>
<button v-on:click="num++">点赞</button>
<h2>
{{name}},有 {{num}} 个人点赞。
</h2>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>

<script>
let app = new Vue({
el: "#app",
data: {
name: "张三",
num: 10
},
methods: {
show() {
return this.name;
},
add() {
this.num++;
}
},
beforeCreate() {
console.log("=========beforeCreate=============");
console.log("数据模型未加载: " + this.name, this.num);
console.log("方法未加载: " + this.show);
console.log("html 模板未加载: " + document.getElementById("num"));
},
created: function () {
console.log("=========created=============");
console.log("数据模型已加载: " + this.name, this.num);
console.log("方法已加载: " + this.show());
console.log("html 模板已加载: " + document.getElementById("num"));
console.log("html 模板未渲染: " + document.getElementById("num").innerText);
},
beforeMount() {
console.log("=========beforeMount=============");
console.log("html 模板未渲染: " + document.getElementById("num").innerText);
},
mounted() {
console.log("=========mounted=============");
console.log("html 模板已渲染: " + document.getElementById("num").innerText);
},
beforeUpdate() {
console.log("=========beforeUpdate=============");
console.log("数据模型已更新: " + this.num);
console.log("html 模板未更新: " + document.getElementById("num").innerText);
},
updated() {
console.log("=========updated=============");
console.log("数据模型已更新: " + this.num);
console.log("html 模板已更新: " + document.getElementById("num").innerText);
}
});
</script>
</body>
</html>

代码运行效果

上述 HTML 代码运行后,浏览器控制台的日志输出如下,在页面中每点赞一次,都会记录显示点赞的总次数。