1. 动态组件

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

1.1 如何实现动态组件渲染

vue 提供了一个内置的 <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
<template>
<div id="app">
<!-- 通过is属性,动态指定要渲染的组件 -->
<component :is="comName"></component>
<!-- 点击按钮,动态切换组件的名称 -->
<button @click="comName = 'LeftCom'">展示 Left 组件</button>
<button @click="comName = 'RightCom'">展示 Right 组件</button>
</div>
</template>

<script>
import LeftCom from './components/Left.vue'
import RightCom from './components/Right.vue'

export default {
name: 'App',
components: {
LeftCom,
RightCom
},
data() {
return {
// 1. 默认渲染的组件名称
comName: 'LeftCom'
}
},
methods: {
}
}
</script>

1.2 使用keep-alive保持状态

默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 <keep-alive> 组件保持动态组件的状态。示例代码如下:

1
2
3
<keep-alive>
<component :is="comName"></component>
</keep-alive>

1.3 keep-alive对应的生命周期函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export default {
name: 'LeftCom',
props: {
},
data() {
return {
}
},
methods: {
},
created() {
console.log('组件被创建了')
},
destroyed() {
console.log('组件被销毁了')
},
activated() {
console.log('LeftCom 组件被激活了')
},
deactivated() {
console.log('LeftCom 组件被缓存了')
}
}

1.4 keep-alive的include属性

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

1
2
3
<keep-alive include="LeftCom,RightCom">
<component :is="comName"></component>
</keep-alive>

2. 插槽

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

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

2.1 体验插槽的基础用法

在封装组件时,可以通过 <slot> 元素定义插槽,从而为用户预留内容占位符。示例代码如下:

子组件

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>
<p>这是 MyCom1 组件的第1个p标签</p>
<!-- 通过 slot 标签,为用户预留内容占位符(插槽) -->
<slot></slot>
<p>这是 MyCom2 组件最后一个p标签</p>
</div>
</template>

<script>
export default {
name: 'TestSlot',
props: {
},
data() {
return {
}
},
methods: {
}
}
</script>

父组件

1
2
3
<TestSlot>
<p>~~~~用户自定义的内容~~~~</p>
</TestSlot>

如果在封装组件时没有预留任何 <slot> 插槽,则用户提供的任何自定义内容都会被丢弃

2.2 后备内容

封装组件时,可以为预留的 <slot> 插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何
内容,则后备内容会生效。示例代码如下:

1
2
3
4
5
6
7
8
<template>
<div>
<p>这是 MyCom1 组件的第1个p标签</p>
<!-- 通过 slot 标签,为用户预留内容占位符(插槽) -->
<slot>这是后备内容</slot>
<p>这是 MyCom2 组件最后一个p标签</p>
</div>
</template>

2.3 具名插槽

如果在封装组件时需要预留多个插槽节点,则需要为每个 插槽指定具体的 name 名称。这种带有具体名称的插槽叫做“具名插槽”。示例代码如下:

子组件

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
<template>
<div>
<!-- 我们希望把页头放这里 -->
<slot name="header"></slot>
<!-- 我们希望把主要内容放这里 -->
<slot>这是后备内容</slot>
<!-- 我们希望把页脚放这里 -->
<slot name="footer"></slot>
</div>
</template>

<script>
export default {
name: 'TestSlot',
props: {
},
data() {
return {
}
},
methods: {
}
}
</script>

父组件引用

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
<template>
<div id="app">
<TestSlot>
<template v-slot:header>
<div>
<h1>滕王阁序</h1>
</div>
</template>
<template v-slot:default>
<div>
<p>主体内容</p>
</div>
</template>
<template v-slot:footer>
<div>
<p>落款:王勃</p>
</div>
</template>
</TestSlot>
</div>
</template>

<script>
import TestSlot from './components/TestSlot.vue'

export default {
name: 'App',
components: {
TestSlot
},
data() {
return {
}
},
methods: {
}
}
</script>

注意:没有指定 name 名称的插槽,会有隐含的名称叫做 default

2.4 具名插槽的简写形式

v-onv-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header可以被重写为 #header

2.5 作用域插槽

在封装组件的过程中,可以为预留的 <slot> 插槽绑定 props 数据,这种带有 props 数据的 <slot> 叫做“作用域插槽”。示例代码如下:

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<slot name="z" v-for="fruit in fruitList" :fruit="fruit"></slot>
</div>
</template>

<script>
export default {
name: 'TestSlot',
props: {
},
data() {
return {
fruitList: ["苹果","柠檬","青柠"]
}
},
methods: {
}
}
</script>

父组件

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
<template>
<div id="app">
<TestSlot>
<!-- 1. 接受作用域插槽对外提供的数据 -->
<template #z="scope">
<div>
<tr>
<!-- 2. 使用作用域插槽的数据 -->
<td>{{ scope.fruit }}</td>
</tr>
</div>
</template>
</TestSlot>
</div>
</template>

<script>
import TestSlot from './components/TestSlot.vue'

export default {
name: 'App',
components: {
TestSlot
},
data() {
return {
}
},
methods: {
}
}
</script>

3. 自定义指令

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

3.1 自定义指令的分类

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

  1. 私有自定义指令
  2. 全局自定义指令

3.2 私有自定义指令

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。示例代码如下:

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
<template>
<div>
<!-- 声明自定义指令时,指令的名字是color -->
<!-- 使用自定义指令时,需要加上 v-指令前缀 -->
<h1 v-color>作用域插槽</h1>
<slot name="z" v-for="fruit in fruitList" :data="fruit"></slot>
</div>
</template>

<script>
export default {
name: 'TestSlot',
props: {
},
data() {
return {
fruitList: ["苹果","柠檬","青柠"]
}
},
methods: {
},
directives: {
color: {
// 为绑定到 html 元素设置红色文字
bind(el) {
// 形参中的 el 是绑定了此指令的、原生的 DOM 对象
el.style.color = 'red'
}
}
}
}
</script>

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

在 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
29
30
31
32
33
34
35
<template>
<div>
<!-- 声明自定义指令时,指令的名字是color -->
<!-- 使用自定义指令时,需要加上 v-指令前缀 -->
<h1 v-color="color">作用域插槽</h1>
<slot name="z" v-for="fruit in fruitList" :data="fruit"></slot>
</div>
</template>

<script>
export default {
name: 'TestSlot',
props: {
},
data() {
return {
fruitList: ["苹果","柠檬","青柠"],
color: 'yellow'
}
},
methods: {
},
directives: {
color: {
// 为绑定到 html 元素设置红色文字
bind(el, binding) {
// 形参中的 el 是绑定了此指令的、原生的 DOM 对象
// 通过 binding 对象的 。value 属性,获取动态的参数值
el.style.color = binding.value
}
}
}
}
</script>

3.4 update函数

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

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
<template>
<div>
<!-- 声明自定义指令时,指令的名字是color -->
<!-- 使用自定义指令时,需要加上 v-指令前缀 -->
<h1 v-color="color">作用域插槽</h1>
<slot name="z" v-for="fruit in fruitList" :data="fruit"></slot>
</div>
</template>

<script>
export default {
name: 'TestSlot',
props: {
},
data() {
return {
fruitList: ["苹果","柠檬","青柠"],
color: 'yellow'
}
},
methods: {
},
directives: {
color: {
// 为绑定到 html 元素设置红色文字
bind(el, binding) {
// 形参中的 el 是绑定了此指令的、原生的 DOM 对象
// 通过 binding 对象的 。value 属性,获取动态的参数值
el.style.color = binding.value
},
// 每次 DOM 更新时被调用
update(el, binding) {
el.style.color = binding.value
}
}
}
}
</script>

3.5 函数简写

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
directives: {
color: {
// 为绑定到 html 元素设置红色文字
bind(el, binding) {
// 形参中的 el 是绑定了此指令的、原生的 DOM 对象
// 通过 binding 对象的 。value 属性,获取动态的参数值
el.style.color = binding.value
},
// 每次 DOM 更新时被调用
update(el, binding) {
el.style.color = binding.value
}
},
// 在 insert 和 update 时,会触发相同的业务逻辑
color2(el, binding) {
el.style.color = binding.value
}
}

3.6 全局自定义指令

全局共享的自定义指令需要通过Vue.directive()进行声明,示例代码如下:

1
2
3
4
5
// 参数1:字符串,表示全局自定义指令的名字
// 参数2:对象,用来接收指令的参数值
Vue.directive('color',function(el, binding){
el.style.color = binding.value
})