0%

前言

因为过滤器和混入这两个在Vue基础学习中(局部使用)已经有过接触,所以在这里就把这两个(全局使用)再讲解一下,加深一下大家的印象。

Vue.filter 的全局使用

注册或获取全局过滤器。

模版代码:

1
2
3
4
// 注册
Vue.filter('toUpperCase', function(val) {
return val.toUpperCase();
})

示例代码:

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>filters 过滤器</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>{{ msg | toUpperCase }}</div>
</div>
<script type="text/javascript">
Vue.filter('toUpperCase', function(val) {
return val.toUpperCase();
})
new Vue({
el: '#app',
data() {
return {
msg: 'abcdefg'
}
}
});
</script>
</body>
</html>

Vue.mixin 的全局使用

全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。插件作者可以使用混入,向组件注入自定义的行为。不推荐在应用代码中使用。

模版代码:

1
2
3
4
5
6
7
8
Vue.mixin({
data() {
return {
name: '张三',
sex: '男'
}
}
})

示例代码:

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>mixin 混入</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>姓名:{{ name }},性别:{{ sex }}</div>
</div>
<script type="text/javascript">
Vue.mixin({
data() {
return {
name: '张三',
sex: '男'
}
}
})
new Vue({
el: '#app'
});
</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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<input v-focus />
</div>
<script type="text/javascript">
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted(el) {
// 聚焦元素
el.focus();
}
})
var vm = new Vue({ el: '#app' });
</script>
</body>
</html>

在浏览器中运行上面的代码,可以看到文本框在页面加载时自动聚焦了。

自定义指令的生命周期(钩子函数)

钩子函数说明:

函数 说明
bind 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
inserted 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
update 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
componentUpdated 指令所在组件的 VNode 及其子 VNode 全部更新后调用
unbind 只调用一次,指令与元素解绑时调用

看钩子函数说明后,再来看个例子:

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>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div v-demo="'demo'">当前数字:{{ num }}</div>
<button @click="num++">累加</button>
<button @click="destroy">解绑</button>
</div>
<script type="text/javascript">
Vue.directive('demo', {
bind(el, bingind){
alert('bind:' + bingind.value);
},
inserted(){
alert('inserted');
},
update(){
alert('update');
},
componentUpdated(){
alert('componentUpdated');
},
unbind(){
alert('unbind');
}
});
var vm = new Vue({
el: '#app',
data() {
return {
num: 0
}
},
methods: {
// 解绑
destroy() {
this.$destroy();
}
}
});
</script>
</body>
</html>

当浏览器加载完成时,页面会先弹出bind:demodemov-demo="'demo'"中的值,关闭弹出框后,继续弹出inserted,关闭后,首次的钩子函数执行完成。
接着我们点击累加按钮,点击完成后,页面弹出update,关闭后继续弹出componentUpdated,到此为止,点击按钮后触发的钩子函数执行完成。
最后,我们点击解绑按钮,页面弹出unbind,紧接着试着点击累加按钮,发现没有任何效果,说明解绑成功。

先来看一个例子

示例代码:

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>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>性别:{{ obj.sex || '未知' }}</div>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data() {
return {
obj: {}
}
}
});
setTimeout(() => {
vm.obj.sex = '男';
}, 2000)

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

让我们先来在浏览器上运行一下上面的代码,页面显示内容性别:未知,过了两秒后,新增一个性别sex的属性,并赋值,但是我们发现页面显示的内容并没有如我们所预期的那样改变。

结论:Vue 无法探测普通的新增属性。

Vue.set 用法

向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。

模版代码:

1
2
3
4
5
// 对应参数:  
// vm.obj {Object | Array} target
// 'sex' {string | number} propertyName/index
// '男' {any} value
Vue.set(vm.obj, 'sex', '男');

示例代码:

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>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>性别:{{ obj.sex || '未知' }}</div>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data() {
return {
obj: {}
}
}
});
setTimeout(() => {
Vue.set(vm.obj, 'sex', '男');
}, 2000)

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

在浏览器中运行上面的代码,刚开始页面显示内容为性别:未知,过了两秒后,页面显示内容为性别:男,内容改变,目的达到。

用法一

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

模版代码:

1
2
3
Vue.nextTick(() => {
alert('数据发生了改变');
}, 1000)

示例代码:

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>{{ msg }}</div>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data() {
return {
msg: '改变前'
}
}
});
setTimeout(() => {
vm.msg = '改变后';
Vue.nextTick(() => {
alert('数据发生了改变');
}, 1000)
})
</script>
</body>
</html>

在浏览器运行一下上面的代码,可以看到,页面先显示了改变前,然后弹出一个框,内容为数据发生了改变,这时先不要关掉弹出框,我们看一下页面的内容还是为改变前,当我们把弹出框关掉后,页面的内容显示为改变后

从例子中我们可以得出:
Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。

用法二

从第一节我们可以知道 Vue 是异步执行 DOM 更新的,既然这样,那么Vue.nextTick是不是就可以作为一个Promise使用呢?下面写一段代码试一下。

模版代码:

1
2
3
4
Vue.nextTick()
.then(() => {
alert('数据发生了改变');
});

示例代码:

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>{{ msg }}</div>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data() {
return {
msg: '改变前'
}
}
});
setTimeout(() => {
vm.msg = '改变后';
Vue.nextTick()
.then(() => {
alert('数据发生了改变');
});
})
</script>
</body>
</html>

在浏览器运行上面的代码,发现和第一节的代码运行结果是一样的。

常用场景

  1. 在视图更新之后,基于新的视图进行操作。比如在在createdmounted阶段。createdmounted都是生命周期,这个会在后面提到,现在可以先记一下,加点印象。
  2. 点击获取元素宽度时会用到,因为我们只要在DOM渲染完成后才能准确获取宽高。

用法

  1. 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
  2. data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app"></div>
<script type="text/javascript">
var Profile = Vue.extend({
template: `<p>My name is {{ name }}, I am {{ age }}<p>`,
data() {
return {
name: 'zhangsan',
age: 18
}
}
});
new Profile().$mount('#app');
</script>
</body>
</html>

打开浏览器运行代码,可以看到页面显示内容为:My name is zhangsan, I am 18.

更进一步

示例代码:

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>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<profile></profile>
</div>
<script type="text/javascript">
var Profile = Vue.extend({
template: `<p>My name is {{ name }}, I am {{ age }}.<p>`,
data() {
return {
name: 'zhangsan',
age: 18
}
}
});
// 看这里
Vue.component('profile', Profile)
new Vue({ el: '#app' });
</script>
</body>
</html>

打开浏览器运行代码,可以看到页面显示内容同样为:My name is zhangsan, I am 18.

总结

通过上面第一节和第二节的示例代码,我们发现Vue.extendVue.component好像有一点关系。现在我们将Vue基础学习中的基础组件代码拿一部分出来比较一下。

先看一下vue.component的用法:

1
2
3
4
5
6
7
8
9
10
11
12
// 自定义组件
Vue.component('v-btn', {
template: `<p>My name is {{ name }}, I am {{ age }}<p>`,
data() {
return {
name: 'zhangsan',
age: 18
}
}
})
// 实例化
new Vue({ el: '#app' });

在看一下vue.extend的用法:

1
2
3
4
5
6
7
8
9
10
var Profile = Vue.extend({
template: `<p>My name is {{ name }}, I am {{ age }}<p>`,
data() {
return {
name: 'zhangsan',
age: 18
}
}
});
new Profile().$mount('#app');

比较后我们发现,vue.extend不需要实例化,只需要通过$mount挂载到自定义的元素上即可。

再结合第二节的示例代码,我们可以得出:

  • vue.extend创建的是一个组件构造器,不是直接挂载在vue的示例对象。简单理解就是vue.extend构造了一个和vue组件内部有一样结构的对象,并且还需要通过Vue.components注册才可以使用的。

mixin混入

混入 (mixin) 提供了一种非常灵活的方式,来分发 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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>mixin 混入</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>姓名:{{ name }},性别:{{ sex }}</div>
</div>
<script type="text/javascript">
var mixin = {
data() {
return {
name: '张三',
sex: '男'
}
}
}
new Vue({
el: '#app',
mixins: [mixin]
});
</script>
</body>
</html>
  • 当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
  • 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。

filters过滤器

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示。

模版代码:

1
2
3
4
5
<!-- 在双花括号中 -->
<div>{{ msg | toUpperCase }}</div>

<!-- 在 `v-bind` 中 -->
<div v-bind:msg="msg | toUpperCase"></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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>filters 过滤器</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>{{ msg | toUpperCase }}</div>
</div>
<script type="text/javascript">
new Vue({
el: '#app',
filters: {
toUpperCase(val) {
return val.toUpperCase();
}
},
data() {
return {
msg: 'abcdefg'
}
}
});
</script>
</body>
</html>

总结

在开发过程中,filters过滤器是比较常用的,比如订单状态,时间格式转换等都会使用一下。而mixin混入用的相对会少一些,虽然mixin混入不需要传递状态,但是也比较容易被滥用。

.native修饰符

我们有时候需要对封装好的组件做一些原生事件的操作,比如点击click事件,这个时候可以使用.native修饰符完成。

模版代码:

1
<v-demo @click.native="onClick"></v-demo>

示例代码:

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>
<head>
<meta charset="utf-8">
<title>.native 修饰符</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<v-btn @click.native="onClick"></v-btn>
</div>
<script type="text/javascript">
var VBtn = {
template: `<button>点击我</button>`
};
new Vue({
el: '#app',
data() {
return {
msg: ''
}
},
components: {
'v-btn': VBtn
},
methods: {
onClick(event) {
alert('我被点击了');
}
}
});
</script>
</body>
</html>

在浏览器运行时,点击按钮时,弹出我被点击了,说明.native修饰符生效。如果去掉.native修饰符后,再次点击按钮没有任何反应。

其他情况

.native修饰符虽然在一定场景下很使用,但是在尝试监听一个类似<input>的元素时,该修饰符不起作用。为了解决这个问题,Vue 提供了一个 $listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。

示例代码:

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>$listeners</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>文本框内容为:{{ msg }}</div>
<v-input @input.native="onInput"></v-input>
</div>
<script type="text/javascript">
var VInput = {
// computed 是计算属性,这个后期会提到
computed: {
inputListeners() {
var vm = this;
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign(
{},
// 从父级添加所有的监听器
vm.$listeners,
{
// 复写 input 事件
input(event) {
vm.$emit('input', event.target.value);
}
}
)
},
},
template: `
<label>
<span>将原生事件绑定到组件:</span>
<input v-on="inputListeners">
</label>
`
};
new Vue({
el: '#app',
data() {
return {
msg: ''
}
},
components: {
'v-input': VInput
},
methods: {
onInput(event) {
this.msg = event.target.value;
}
}
});
</script>
</body>
</html>

题外:.sync修饰符

  1. .sync 修饰符是为了解决prop进行‘双向绑定’的问题。
  2. 需要注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。

模版代码:

1
<v-demo :title.sync="msg"></v-demo>

示例代码:

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>.sync 修饰符</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div>父组件:{{ msg }}</div>
<v-demo :title.sync="msg"></v-demo>
</div>
<script type="text/javascript">
var VDemo = {
props: [ 'title' ],
template: `<div>{{ title }}</div>`,
mounted() {
setTimeout(() => {
this.$emit('update:title', 123)
}, 2000);
}
};
new Vue({
el: '#app',
data() {
return {
msg: '我是标题'
}
},
components: {
'v-demo': VDemo
}
});
</script>
</body>
</html>

总结

在日常开发中,将input封装成组件这种事还是很常见的,所以第二节中的示例代码还是需要多看看多练习一下。

定义组件名

两种方式:

1
2
3
4
5
// 使用 kebab-case
Vue.component('my-component-name', { /* ... */ })

// 使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })

组件的注册

组件的注册分为全局注册局部注册两种

1. 全局注册:

代码模版:

1
2
Vue.component('ComponentName', {...});
new Vue({ el: '#app' })

在组件基础中的所有代码都为全局注册,这里就不再写示例代码,如有需要可以返回查看。

2. 局部注册

代码模版:

1
2
3
4
5
6
7
8
9
10
11
12
// 编写 v-demo 组件
var VDemo = {
template: `<h1>我是标题</h1>`
};

// 注册 v-demo 组件
new Vue({
el: '#app',
components: {
'v-demo': VDemo
}
});

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>深入了解组件</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<v-demo></v-demo>
</div>
<script type="text/javascript">
var VDemo = {
template: `<h1>我是标题</h1>`
};
new Vue({
el: '#app',
components: {
'v-demo': VDemo
}
});
</script>
</body>
</html>

3. 全局注册 和 局部注册 的区别

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

通过 Prop 向子组件传递数据

模版写法:

1
2
3
4
Vue.component('v-h1', {
props: ['msg'],
template: `<h1>{{ msg }}</h1>`
});

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>组件基础</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<v-h1 msg="我是标题"></v-h1>
</div>
<script type="text/javascript">
Vue.component('v-h1', {
props: ['msg'],
template: `<h1>{{ msg }}</h1>`
});
new Vue({ el: '#app' });
</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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>组件基础</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div :style="{ fontSize: fontSize + 'em' }">
<v-p @big-font="fontSize++"></v-p>
</div>
</div>
<script type="text/javascript">
Vue.component('v-p', {
template: `
<div>
<p>我是标题</p>
<button @click="$emit('big-font')">放大文字</button>
</div>
`
});
new Vue({
el: '#app',
data() {
return {
fontSize: 1
}
}
});
</script>
</body>
</html>

通过上面的代码可以发现,子组件可以通过调用内建的$emit方法并传入事件名称来触发一个事件,代码片段:

1
2
3
4
5
<!-- 子组件 -->
<button @click="$emit('big-font')">放大文字</button>

<!-- 父组件 -->
<v-p @big-font="fontSize++"></v-p>

使用事件抛出一个值

示例代码:

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>
<head>
<meta charset="utf-8">
<title>组件基础</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<div :style="{ fontSize: fontSize + 'em' }">
<v-p @big-font="fontSize += $event"></v-p>
</div>
</div>
<script type="text/javascript">
Vue.component('v-p', {
template: `
<div>
<p>我是标题</p>
<button @click="$emit('big-font', 0.1)">放大文字</button>
</div>
`
});
new Vue({
el: '#app',
data() {
return {
fontSize: 1
}
}
});
</script>
</body>
</html>

代码片段:

1
2
3
4
5
<!-- 子组件 -->
<button @click="$emit('big-font', 0.1)">放大文字</button>

<!-- 父组件 -->
<v-p @big-font="fontSize += $event"></v-p>

跟上面的例子做对比可以发现,使用事件抛出一个值时,只需在$emit上添加多一个参数即可,接收时使用$event进行接收。

通过插槽分发内容

在编写组件的过程中,有时我们希望组件里面能自己定义一些HTML元素,这时我们可以使用<slot>实现。

示例代码:

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>组件基础</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<v-demo>
<p style="color: red;">我是内容</p>
</v-demo>
</div>
<script type="text/javascript">
Vue.component('v-demo', {
template: `
<div>
<h1>我是标题</h1>
<slot></slot>
</div>
`
});
new Vue({ el: '#app' });
</script>
</body>
</html>

基本示例

  1. 在Vue中,组件是一个很强大的功能,组件可以扩展HTML元素,封装可重用的代码。比如在页面中的某一部分需要在多个场景中使用,那么我们可以将需要部分提取出来,从而提高代码的复用率。
  2. 所有的Vue组件都是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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>组件基础</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>
<body>
<div id="app">
<v-btn></v-btn>
<hr />
<v-btn></v-btn>
</div>
<script type="text/javascript">
// 自定义组件
Vue.component('v-btn', {
data: function () {
return {
count: 0
}
},
template: `
<div>
<h1>{{ count }}</h1>
<button @click='count++'>点击我试试</button>
</div>
`
})
// 实例化
new Vue({ el: '#app' });
</script>
</body>
</html>

data 必须是一个函数

在上面的示例代码中,我们可以看到有一段代码是这样的:

1
2
3
4
5
data: function () {
return {
count: 0
}
},

那么,为什么data中的数据需要使用函数return回来,而不直接使用Object形式呢?

  1. vue组件中data值不能为对象,因为对象是引用类型,组件可能会被多个实例同时引用。如果data值为对象,将导致多个实例共享一个对象,其中一个组件改变data属性值,其它实例也会受到影响。
  2. data为函数,通过return 返回对象的拷贝,致使每个实例都有自己独立的对象,实例之间可以互不影响的改变data属性值。

例子一【引用类型】:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>例子</title>
</head>
<body>
<div id="app"></div>
<script>
const fn = function() {};
fn.prototype.data = { a: 1 };
const demo01 = new fn();
const demo02 = new fn();
demo01.data.a = 5;
document.getElementById('app').innerHTML = `
<div>[demo02.data.a]:${demo02.data.a}</div>
`;

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

打开浏览器运行上面的代码:可以看到页面显示内容为:

1
[demo02.data.a]:5

为什么demo02.data.a不是1呢?

  • 因为每个组件的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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>例子</title>
</head>
<body>
<div id="app"></div>
<script>
const fn = function() {
this.data = this.data();
};
fn.prototype.data = function() {
return { a: 1 };
};
const demo01 = new fn();
const demo02 = new fn();
demo01.data.a = 5;
document.getElementById('app').innerHTML = `
<div>[demo02.data.a]:${demo02.data.a}</div>
`;

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

打开浏览器运行上面的代码:可以看到页面显示内容为:

1
[demo02.data.a]:1

为什么demo02.data.a1呢?

  • 因为data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响