动态组件

什么是动态组件

动态组件指的是动态切换组件的显示与隐藏

动态组件的渲染

vue 提供了一个内置的 <component> 组件,专门用来实现动态组件的渲染,它就相当于组件的占位符,提供一个 is 属性用来指定该位置实际需要渲染的组件,通过控制 is 属性的值可以实现组件的切换

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
<template>
<div class="app-container">
<h1>App 根组件</h1>
<button @click="comName = 'Left'">展示 Left</button>
<button @click="comName = 'Right'">展示 Right</button>
<div class="box">
<!-- 渲染 Left 组件和 Right 组件 -->
<!-- 1. component 标签是 vue 内置的,作用:组件的占位符 -->
<!-- 2. is 属性的值,表示要渲染的组件的名字 -->
<component :is="comName"></component>
</div>
</div>
</template>
<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
data() {
return {
// comName 表示要展示的组件的名字
comName: 'Left'
}
},
components: {
Left,
Right
}
}
</script>

使用 keep-alive 保持动态组件状态

在上面使用<component> 标签来切换组件时,默认情况下,被切换的组件时无法保持组件的状态。此时可以使用 vue 内置的 <keep-alive> 组件保持动态组
件的状态,在使用时,我们只需要使用<keep-alive><component> 组件包裹起来就好了

1
2
3
4
<!-- keep-alive 会把内部的组件进行缓存,而不是销毁组件 -->
<keep-alive>
<component :is="comName"></component>
</keep-alive>

keep-alive 的生命周期

当组件被keep-alive包裹时,会多两个生命周期函数 deactivatedactivated

当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
当组件被激活时,会自动触发组件的 activated 生命周期函数。

我们只需要给被包含的组件定义这两个函数就可以进行使用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default {
data() {
return {
count: 0
}
},
created() {
console.log('Left 组件被创建了!')
},
destroyed() {
console.log('Left 组件被销毁了~~~')
},
// 当组件第一次被创建的时候,既会执行 created 生命周期,也会执行 activated 生命周期
// 当时,当组件被激活的时候,只会触发 activated 生命周期,不再触发 created。因为组件没有被重新创建
activated() {
console.log('组件被激活了,activated')
},
deactivated() {
console.log('组件被缓存了,deactivated')
}
}
</script>

keep-alive 的include和exclude属性

include 属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔
exclude 属性用来指定:名称匹配的组件不会被缓存。多个组件名之间使用英文的逗号分隔

不要同时使用 include 和 exclude 这两个属性

1
2
3
4
5
<!-- 在使用 keep-alive 的时候,可以通过 include 指定哪些组件需要被缓存; -->
<!-- 或者,通过 exclude 属性指定哪些组件不需要被缓存;但是:不要同时使用 include 和 exclude 这两个属性 -->
<keep-alive exclude="MyRight">
<component :is="comName"></component>
</keep-alive>

组件的声明和注册名称

注册名称:就是我们在使用该组件时,父组件的 components 节点中写的名称

声明名称:我们可以在组件声明(即编写封装组件的.vue文件时)通过 name 节点来给组件指定的名称,我们建议每个封装的组件都有自己的声明的名称

如果在声明组件的时候,没有为组件指定 name 名称,则组件的名称默认就是注册时候的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
// 在App这个根组件中使用其他组件
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
data() {
return {
// comName 表示要展示的组件的名字
comName: 'Left'
}
},
components: {
// 如果在“声明组件”的时候,没有为组件指定 name 名称,则组件的名称默认就是“注册时候的名称”
Left,
Right
}
}
</script>
1
2
3
4
5
6
7
8
9
10
<script>
// 声明组件时通过name来给组件声明名称
export default {
// 当提供了 name 属性之后,组件的名称,就是 name 属性的值
// 对比:
// 1. 组件的 “注册名称” 的主要应用场景是:以标签的形式,把注册好的组件,渲染和使用到页面结构之中
// 2. 组件声明时候的 “name” 名称的主要应用场景:结合 <keep-alive> 标签实现组件缓存功能;以及在调试工具中看到组件的 name 名称
name: 'MyRight'
}
</script>

插槽

插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。

可以把插槽认为是组件封装期间,为用户预留的内容的占位符。

插槽快速使用

在封装组件时,可以通过 元素定义插槽,从而为用户预留内容占位符

在声明left.vue组件时,通过 <slot> 来定义插槽

1
2
3
4
5
6
7
<template>
<div class="left-container">
<h3>Left 组件</h3>
<!-- 声明一个插槽区域 -->
<slot> </slot>
</div>
</template>

app.vue 组件中使用

1
2
3
<left>
<p>这是在left组件的内容区域生命的p标签</p>
</left>

v-slot 指令

我们定义插槽时,官方规定每个插槽都要有一个 name 名称,如果省略了 slot 的 name 属性,则有一个默认名称叫做 default,默认情况下,在使用组件的时候,提供的内容都会被填充到名字为 default 的插槽之中,当我们有很多插槽时,我们应该给每个插槽一个不同的 name ,这样有自己名字的插槽叫做 具名插槽

在定义插槽时,可以通过在slot标签内部写入信息,指定该插槽的后备内容,当使用了该插槽但没有指定插入内容时,会显示后备内容

比如在声明left.vue组件时,通过 name 来定义插槽名称

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div class="left-container">
<h3>Left 组件</h3>
<hr />
<!-- 声明一个插槽区域 -->
<!-- vue 官方规定:每一个 slot 插槽,都要有一个 name 名称 -->
<!-- 如果省略了 slot 的 name 属性,则有一个默认名称叫做 default -->
<slot name="default">
<h6>这是 default 插槽的后备内容</h6>
</slot>
</div>
</template>

当我们有很多插槽时,我们可以通过v-slot指令来指定需要进行操作的插槽

v-slot: 指令不能直接用在元素身上,必须用在 template 标签上

v-slot: 指令的简写形式是 #

app.vue 组件中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="app-container">
<div class="box" style="display: none;">
<!-- 渲染 Left 组件和 Right 组件 -->
<Left>
<!-- 1. 如果要把内容填充到指定名称的插槽中,需要使用 v-slot: 这个指令 -->
<!-- 2. v-slot: 后面要跟上插槽的名字 -->
<!-- 3. v-slot: 指令不能直接用在元素身上,必须用在 template 标签上 -->
<!-- 4. template 这个标签,它是一个虚拟的标签,只起到包裹性质的作用,但是,不会被渲染为任何实质性的 html 元素 -->
<!-- 5. v-slot: 指令的简写形式是 # -->
<template #default>
<p>这是在 Left 组件的内容区域,声明的 p 标签</p>
</template>
</Left>
</div>
</div>
</template>

作用域插槽

在封装组件时,为预留的slot提供属性对应的值,这种用法叫做作用域插槽

在下方实例中,给slot提供了msg这个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="content-box">
<!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” -->
<slot name="content" msg="hello vue.js" :user="userinfo"></slot>
</div>
<script>
export default {
// 首字母要大写
name: 'Article',
data() {
return {
userinfo: {
name: 'zs',
age: 20
}
}
}
}
</script>

当我们使用时,可以通过 = 来接收该属性值和其数据,scope就是个形参变量,想写什么都好,习惯使用 scope

1
2
3
4
5
<template #content="scope">
<div>
<p>{{ scope.msg }}</p>
</div>
</template>

同样,也可以使用解构赋值的方法来使用该数据

1
2
3
4
5
6
<template #content="{ msg, user }">
<div>
<p>{{ msg }}</p>
<p>{{ user.name }}</p>
</div>
</template>

自定义指令

什么是自定义指令

vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。

自定义指令的分类

vue 中的自定义指令分为两类,分别是:

  1. 私有自定义指令

  2. 全局自定义指令

私有自定义指令

在每个vue组件之中,可以在 directives 节点下声明私有自定义指令

在下面代码中,自定义一个 v-color 指令来改变标签颜色

当指令第一次被绑定到元素上的时候,会立即触发 bind 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div class="app-container">
<h1 v-color>App 根组件</h1>
</div>
</template>
<script>
export default {
// 私有自定义指令的节点
directives: {
// 定义名为 color 的指令,指向一个配置对象
color: {
// 当指令第一次被绑定到元素上的时候,会立即触发 bind 函数
// 形参中的 el 表示当前指令所绑定到的那个 DOM 对象
bind(el) {
console.log('触发了 v-color 的 bind 函数')
el.style.color = 'red'
}
}
}
}
</script>

为自定义指令动态绑定参数值

在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值

并且在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值,习惯性的第二个参数我们叫做 binding

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
<template>
<div class="app-container">
<h1 v-color="color">App 根组件</h1>
<p v-color="'red'">测试</p>
</div>
</template>
<script>
export default {
data() {
return {
color: 'blue'
}
},
// 私有自定义指令的节点
directives: {
// 定义名为 color 的指令,指向一个配置对象
color: {
// 当指令第一次被绑定到元素上的时候,会立即触发 bind 函数
// 形参中的 el 表示当前指令所绑定到的那个 DOM 对象
bind(el, binding) {
console.log(binding)
console.log('触发了 v-color 的 bind 函数')
el.style.color = binding.value
}
}
}
}
</script>

指令的update函数

bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。 update 函数会在每次 DOM 更新时被调用。

下面实现点击按钮后,颜色数据改变,同时页面标签颜色渲染改变,指令的 update函数,当元素变化时,所有使用该指令的组件都会触发update函数

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
<template>
<div class="app-container">
<h1 v-color="color">App 根组件</h1>
<p v-color="'red'">测试</p>
<button @click="color = 'green'">改变 color 的颜色值</button>
</div>
</template>
<script>
export default {
data() {
return {
color: 'blue'
}
},
// 私有自定义指令的节点
directives: {
// 定义名为 color 的指令,指向一个配置对象
color: {
// 当指令第一次被绑定到元素上的时候,会立即触发 bind 函数
// 形参中的 el 表示当前指令所绑定到的那个 DOM 对象
bind(el, binding) {
console.log(binding)
console.log('触发了 v-color 的 bind 函数')
el.style.color = binding.value
},
// 在 DOM 更新的时候,会触发 update 函数
update(el, binding) {
console.log('触发了 v-color 的 update 函数')
el.style.color = binding.value
}
}
}
}
</script>

自定义指令的简写

我们会发现 bindupdate 这两个函数,除了函数名不同,其他都相同,写两遍比较麻烦,所以vue提供了自定义指令的简写方式

如果 bindupdate 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="app-container">
<h1 v-color="color">App 根组件</h1>
<p v-color="'red'">测试</p>
<button @click="color = 'green'">改变 color 的颜色值</button>
</div>
</template>
<script>
export default {
data() {
return {
color: 'blue'
}
},
// 私有自定义指令的节点
directives: {
color(el, binding) {
console.log(binding)
el.style.color = binding.value
}
}
}
</script>

全局自定义指令

上面在组件的.vue 文件中定义的指令只能在自己的组件中使用,我们可以像定义全局过滤器一样在main.js中定义全局自定义指令

1
2
3
4
5
6
7
8
9
10
11
12
// 全局自定义指令
/* Vue.directive('color', {
bind(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
el.style.color = binding.value
}
}) */
Vue.directive('color', function(el, binding) {
el.style.color = binding.value
})