全局 API 是什么

举个比较好理解的例子,Vue 就好比一块蛋糕,生命周期钩子函数以及内部指令可以理解为做蛋糕用的面粉、糖、鸡蛋等。而全局 API 就是裹在蛋糕外面的奶油,让整个蛋糕(Vue)看起来更加美味。全局 API 的作用就是给 Vue 以更多的自由,大家可以根据自己项目的需求,通过全局 API 来制作出各种各样的方法工具。

Vue.extend

Vue.extend 是什么?

作为全局 API 中的一员,在实际开发中很少会被用到,因为相比我们经常使用的 Vue.componentVue.extend 在写法上就会显得比较繁琐。但在一些比较特殊的场景下,Vue.extend + $mount 是我们需要去了解的。

自定义纯标签

假设我现在有个需求,在很多地方需要用到我的官网名称,并且这个官网名称还带上 url 地址,可点击跳转到我的官网,在模板中,只需要写一个 <official/> 就能展示。

让我们来看看用 Vue.extend 怎么去实现,新建 demo.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>Vue.extend-扩展实例构造器</title>
</head>

<body>
<h1>Vue.extend-扩展实例构造器</h1>
<official></official>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script type="text/javascript">
var official = Vue.extend({
template: "<p><a target='_blank' :href='url'>{{name}}</a></p>",
data: function () {
return {
name: 'Jungle\'Blog',
url: 'https://blog.csdn.net/weixin_43825727?spm=1011.2124.3001.5343'
}
}
});
new official().$mount('official');
</script>
</body>

</html>

image-20210318105513035

全局定义之后,可以在任何地方进行使用,非常便捷

Vue.directive 自定义指令

Vue.directive 是什么

之前也学了 v-modelv-show 等官方定义的指令,在项目的开发过程中,我们会有一些特殊的需求,要自定义指令,Vue.directive 就是为什么做这件事情的 API。

自定义组件

假如又来了一个需求,需要让加上 v-color 指令的标签的字体颜色通过传入的变量值进行改变,比如 v-color="red" 标签就会变为红色。

代码演示如下:

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">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue.directive 自定义标签</title>
<style>
</style>
</head>

<body>
<div id='app'>
<p v-color="color">啦啦啦啦啦</p>
</div>
</body>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script>
window.onload = function () {
Vue.directive('color', function (el, binding, vnode) {
console.log('el', el) // 被绑定的node节点
console.log('binding', binding) // 一个对象,包含指令的很多信息
console.log('vnode', vnode) // Vue编译生成的虚拟节点
el.style = "color:" + binding.value
});

new Vue({
el: '#app',
data: {
color: 'red'
}
})
}
</script>

</html>

image-20210318105603726

回调函数参数

自定义组件的回调函数有三个参数。

  • el:被绑定的 node 节点。
  • binding: 包含指令信息的对象参数。
  • vnode:Vue 编译生成的虚拟节点。

自定义组件的生命周期

自定义组件包含几个生命周期,也就是在调用自定义组件时,几个钩子函数会被触发,分别是如下。

  • bind:只会调用一次,在第一次绑定到元素上时被调用,初始化操作可以使用它。
  • inserted:被绑定的元素插入了父节点。
  • update:被绑定的元素模板更新时调用。
  • componentUpdated:被绑定的元素模板完成一次生命周期。
  • unbind:指令和被绑定元素解绑时调用。

Vue.set 全局操作

在解释 Vue.set 之前先了解一下 Vue 的响应式原理:

当你把一个 JS 对象传给 Vue 实例的 data 属性时,Vue 将遍历此对象的所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本的浏览器。

为什么要使用 Vue.set

受限于现代浏览器,Vue 检测不到对象的添加和删除;因为 Vue 在初始化实例时对 data 属性执行 getter/setter 转化操作,所以对象必须在 data 中才能让其响应式。

Vue 不允许在已经创建的实例上动态添加新的根级响应式属性,不过可以使用 Vue.set 方法将响应式属性添加到嵌套的对象上。代码演示如下:

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">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue.set 全局操作</title>
<style>
</style>
</head>
<body>
<div id='app'>
<p v-for="item in fruit">{{ item }}</p>
<button v-on:click="change"></button>
{{ fruit }}
</div>
<button onclick="add()">外部添加</button>
</body>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script>
function add(){
// app.fruit[1]='ddd';
Vue.set(app.fruit, 1, 'melon');
console.log(app.fruit)
}
var app = new Vue({
el: '#app',
data: {
fruit: ['apple', 'banana', 'pear', 'grape']
},
methods: {
change: function () {
this.fruit[1] = 'melon'
}
}
})
</script>
</html>

image-20210318105653417
image-20210318105719418

当点击按钮“变”的时候,渲染没有变化,当点击按钮“外部添加”的时候,渲染数据发生了变化。

Vue.filter

全局过滤器是一个在项目中时常会用到的 API,使用场景也非常丰富,比如说数据的统计,保留小数点参数等等,在后续的商场实战中也会被运用到实战中。

实例

下面我们演示一个简单的过滤器,需求是使用过滤器将需要过滤的目标值加上 4,代码演示如下:

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 name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue.filter 过滤器</title>
<style>
</style>
</head>

<body>
<div id='app'>
<p>{{ count | add5 }}</p>

</div>
</body>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script>
Vue.filter("add5", function (value) {
return value + 5;
})

var app = new Vue({
el: '#app',
data: {
count: 20
},
})
</script>

</html>

image-20210318105752313

屏幕上会出现 25 这个数字,注意的是,声明 add5过滤器,必须要放在声明实例 app 之前,否则不会被注入到实例中。

Vue.nextTick

nextTick 是一个比较重要的高级特性,应用场景在很多地方会出现。Vue 是异步渲染的框架,一开始就是如此,当你改变 data 属性内部的变量,视图不会立即更新,在此时你若是进行 DOM 操作,是拿不到最新的渲染结果,这个时候你就要借助 Vue.nextTick 高级特性,在该特性的回调函数内获取最新的渲染结果。

实例

1
2
3
this.$nextTick(function () {
const html = document.getElementById("aaa").innerHTML
})

Mixin 混入

混入(mixin)提供了一个非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包括任意组件选项。当组件使用混入对象时,所有混入对象的选项将被「混合」进入该组件本身的选项。

上面这段摘录自 Vue 的官方文档,文字有点难理解,通过举实例来解释 mixin 的具体用法。 其实我们可以用 Object 的思想去理解 mixin,假设有一个变量 A,它的值为 { a: 1, b: 2 },有另一个变量 B,它的值为 { a: 3, c: 4 },B 作为混入的对象混入 A,那么 A 中已有的属性 a 不会被覆盖,A 会新增一个 c 属性,最终 A 为 { a: 1, b: 2, c: 4 }

实例

通过 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
84
85
86
// mixin.js
export default {
data () {
return {
username: '张三',
age: 30,
hasWife: true
}
},
mounted() {
console.log('mixin')
},
methods: {
speak() {
console.log('这是mixin')
},
cry() {
console.log('这是cry')
}
}
}

// App.vue
import mixin from './minix';
export default {
data() {
return {
username: '李四',
age: 31,
hasHusband: true
}
},
mounted() {
console.log('app');
},
mixins: [mixin],
methods: {
speak() {
console.log('这是app')
},
eat() {
console.log('吃饭')
}
}
}

// 最终得到的结果如下
export default {
data() {
return {
name: '李四', // name为共有属性,最终保留 app.vue 的
age: 31, // 同上
hasHusband: true, // app.vue 独有属性,保留
hasWife: true, // app.vue 没有的属性,会被添加进来
}
},
mounted() {
// 在钩子函数中的,会被合并到 app.vue 的钩子函数中,minix中的代码会在前面
console.log('mixin')
console.log('app')
},
methods: {
// 同名钩子函数将合并为一个数组,而methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
speak () {
[
function() {
console.log('这是mixin')
},
function() {
console.log('这是app')
}
].forEach(cb => {
cb()
})
console.log('这是app')
},
// 自身独有的,保留
eat() {
console.log('吃饭')
},
// 自身没有的方法,会被添加进来
cry() {
console.log('这是cry')
}
}
}

上述为组件中使用 mixins 方法进行「混入」,下面我们来介绍「全局混入」,「混入」可以在入口页进行全局注册,但是使用它时要格外小心,一旦使用全局混入,它将会影响每一个之后创建的 Vue 实例。 官方示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})

new Vue({
myOption: 'hello!'
})
// => "hello!"

Vue 生命周期函数

单页面开发模式,每个页面都可以理解为一个 Vue 组件,每个页面都是一个鲜活的生命,在它的一生中,从出生到消亡都会有对应的钩子函数,下面就让我们认识一下它们。

  • beforeCreate:在组件创建之前。
  • created:在组件创建之后,一般用于初始化一些固定的数据。
  • beforeMount:DOM 节点渲染之前。
  • mounted:在 DOM 节点渲染完之后被触发,通常笔者都会在这个生命周期中通过 ajax 去获取服务端的数据,如果在 created 生命周期里获取数据并渲染视图,DOM 节点可能还未被渲染,有可能页面会报错,安全起见,数据统一放在 mounted 钩子函数内获取。
  • beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  • updated:当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用 计算属性watcher 取而代之。
  • activated:被 keep-alive 缓存的组件激活时调用。
  • deactivated:被 keep-alive 缓存的组件停用时调用。
  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed:实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。一般用于销毁页面内创建的 setTimeout 等变量,防止内存泄漏。

实例

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
<!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>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id='app'>
<p v-cloak>{{count}}</p>
<button @click="add">加+</button>
<button onclick="app.$destroy()">销毁</button>
</div>
</body>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script>
var app=new Vue({
el:'#app',
data:{
count: 1
},
methods:{
add: function () {
this.count ++;
}
},
beforeCreate:function(){
console.log('beforeCreate');
},
created:function(){
console.log('created');
},
beforeMount:function(){
console.log('beforeMount');
},
mounted:function(){
console.log('mounted');
},
beforeUpdate:function(){
console.log('beforeUpdate');
},
updated:function(){
console.log('updated');
},
activated:function(){
console.log('activated');
},
deactivated:function(){
console.log('deactivated');
},
beforeDestroy:function(){
console.log('beforeDestroy');
},
destroyed:function(){
console.log('destroyed')
}

})
</script>
</html>

image-20210318105924927
image-20210318105947965
image-20210318110025897

每个生命周期都值得被深入研究

总结

Vue 全局 API 属于 Vue 的高级特性,随着项目复杂度的提升,这些高级特性出现的频率也会随之增高,学习它们也是必不可少的,在学习过程中还需用心体会它们解决问题的场景。