1. 动态组件
1.1 什么是动态组件
动态组件指的是动态切换组件的显示与隐藏。
1.2 如何实现动态组件渲染
vue 提供了一个内置的 <component> 组件,专门用来实现动态组件的渲染。示例代码如下:
<template>
<div class="app-container">
<h1>App 根组件</h1>
<hr />
<button @click="flag = 'Left'">展示左组件</button>
<button @click="flag = 'Right'">展示右组件</button>
<!-- component 标签通过 is 属性动态指定要渲染的组件 -->
<componnet :is="flag"></componnet>
</div>
</template>
<script>
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";
export default {
components: {
Left,
Right,
},
data() {
return {
flag: "Left",
};
},
};
</script>
<componnet :is="flag"></componnet> 的 flag 如果是 Left ,相当于 <Left></Left>
所以 <componnet> 标签相当于就是组件的占位符,is 属性的值就是要渲染的组件名字,即需要在 components 节点下注册的名称。
1.3 切换后无法保持状态
默认情况下,切换动态组件时无法保持组件的状态,例如:将左组件数据加到 5,切换到右组件再切回来,左组件数据又重新回到默认值。此时可以使用 vue 内置的 <keep-alive> 组件保持动态组件的状态。示例代码如下:
<keep-alive>
<componnet :is="flag"></componnet>
</keep-alive>
切换后无法保持状态的根本原因是:切换后,原来的组件会被销毁(生命周期),切换回来又被重新创建,所以原先的数据就无法保存,而是回到默认值。
<keep-alive> 原理是:将其标签内部的组件进行缓存,跳过了销毁组件这个流程
1.4 keep-alive 对应的生命周期函数
当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
当组件被激活时,会自动触发组件的 activated 生命周期函数。
我们在左组件写四个生命周期函数:
created() {
console.log("Left created");
},
destroyed() {
console.log("Left destroyed");
},
activated() {
console.log("Left activated");
},
deactivated() {
console.log("Left deactivated");
},
一开始组件从无到右数据变化会触发 created 生命周期,且该组件会激活,所以触发 activated 生命周期。
切换到右组件,左组件被缓存,所以触发了 deactivated 生命周期函数,再切换回来,左组件再次被激活,再次触发 activated 生命周期函数。后面就是 activated 和 deactivated 随着切换,不断反复出现。
1.5 keep-alive 的 include 属性
include 属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔:
<keep-alive include="Left">
<componnet :is="flag"></componnet>
</keep-alive>
该代码表示只有 Left 组件需要被缓存,没写到的 Right 组件在切换时就会被销毁。
同样与 include 相对的是 exclude,表示只有名称匹配的组件不会被缓存。用法与 include 类似,但两者不能同时存在。
2. 插槽
2.1 什么是插槽
插槽(Slot) 是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。
可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
2.2 插槽的基础用法
在封装组件时,可以通过 <slot> 元素定义插槽,从而为用户预留内容占位符。示例代码如下:
右组件:
<template>
<div class="right-container">
<h3>Right 组件</h3>
<!-- 插槽 -->
<slot></slot>
<h3>Right 组件底部</h3>
</div>
</template>
App.vue:
<Right>
<p>111111</p>
</Right>
最终结果:
最终 <slot><slot/> 位置被 <p>111111</p> 替换。这就像我们小时候玩游戏的游戏机卡带,如果需要玩超级玛丽需要插个卡带,需要玩魂斗罗又要重新插个卡带。卡带是我们自定义的,游戏机留着这个位置,让我们想玩什么就插入什么卡带。
同理,<slot> 标签相当于游戏机上的卡槽,App.vue 上对应组件内部内容为游戏机的卡带,里面是什么就呈现什么。
2.3 没有预留插槽的内容会被丢弃
如果在封装组件时没有预留任何 <slot> 插槽,则用户提供的任何自定义内容都会被丢弃。将之前 2.2 <slot><slot/> 去掉,会发现 111111 内容不再体现。
很简单的道理,你游戏机卡槽坏了,你有卡带游戏也玩不了。
2.4 后备内容
封装组件时,可以为预留的 <slot> 插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。示例代码如下:
右组件:
<template>
<div class="right-container">
<h3>Right 组件</h3>
<!-- 插槽 -->
<slot>
<h3>我是后备内容</h3>
</slot>
<h3>Right 组件底部</h3>
</div>
</template>
App.vue:
<!-- 没有为插槽提供任何内容 -->
<Right></Right>
最终结果:
还是用游戏机来举例,游戏机自带一个卡带,如果我们没有使用自己的卡带,那就只能玩它这个自带卡带里的游戏。如果我们使用自己的卡带,那么玩的游戏就是我们自己卡带的。
2.5 具名插槽
如果在封装组件时需要预留多个插槽节点,则需要为每个 <slot> 插槽指定具体的 name 名称。这种带有具体名称的插槽叫做具名插槽。示例代码如下:
右组件:
<template>
<div class="right-container">
<h3>Right 组件</h3>
<!-- 插槽 -->
<slot name="poemTitle"></slot>
<slot name="poemContent"></slot>
<slot></slot>
<h3>Right 组件底部</h3>
</div>
</template>
App.vue:
<Right>
<template v-slot:poemTitle>
<h3>静夜思</h3>
</template>
<template v-slot:poemContent>
<p>窗前明月光,疑是地上霜。</p>
</template>
<template>
<p>我是 default</p>
</template>
</Right>
最终:
注意:
- 没有指定
name名称的插槽,会有隐含的名称叫做default,这也是为什么没有名字的插槽可以渲染到页面上的原因
2.6 v-slot 简写
跟 v-on 和 v-bind 一样,:v-slot 也有缩写,即字符 #。
简化前:
<Right>
<template v-slot:poemTitle>
<h3>静夜思</h3>
</template>
<template v-slot:poemContent>
<p>窗前明月光,疑是地上霜。</p>
</template>
</Right>
简化后:
<Right>
<template #poemTitle>
<h3>静夜思</h3>
</template>
<template #poemContent>
<p>窗前明月光,疑是地上霜。</p>
</template>
<p>我是 default</p>
</Right>
2.7 作用域插槽
在封装组件的过程中,可以为预留的 <slot> 插槽绑定 props 数据,这种带有 props 数据的 <slot> 叫做作用域插槽。
子组件 Right.vue:
<slot name="message" info="fsda"></slot>
父组件 App.vue:
<Right>
<template #message="scope">
<p>{{ scope.info }}</p>
</template>
</Right>
子组件通过插槽的自定义属性可以传值给父组件,父组件在使用插槽时,一般需要指定名字,如上面代码中父组件使用插槽就需要 #message,其值就为子组件插槽的自定义属性的键值对。
如上面代码中,scope 接收的对象为:
{ "info": "fsda" }
不传值默认对象就为 {}
2.8 解构插槽
作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。
子组件 Right.vue:
<slot name="message" info="abcd" :msg="userinfo"></slot>
export default {
data() {
return {
userinfo: {
name: "zs",
age: 20,
},
};
},
}
父组件 App.vue:
<Right>
<template #message="{ info, msg }">
<p>{{ info }}</p>
<p>{{ msg.name }}</p>
<p>{{ msg.age }}</p>
</template>
</Right>
通过对键的解构,我们可以直接获取其键的值,然后再进行操作会更加简单。
3. 自定义属性
3.1 什么是自定义指令
vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。
3.2 自定义指令的分类
vue 中的自定义指令分为两类,分别是:
- 私有自定义指令
- 全局自定义指令
3.3 私有自定义指令
在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。示例代码如下:
directives: {
color: {
// el 代表绑定到的元素,所以 el 是原生的 DOM 对象
bind(el) {
el.style.color = "red"; // 绑定到的元素颜色变红
},
},
},
使用如下:
<h3 v-color>Right 组件</h3>
注意:
- 使用时需要以
v-开头 - 私有自定义组件只能在自身使用
- 自定义指令第一次绑定到元素上,会立即触发
bind函数
3.4 为自定义指令动态绑定参数值
在 template 结构中使用自定义指令时,可以通过等号 = 的方式,为当前指令动态绑定参数值:
<h3 v-color="color">Right 组件</h3>
data() {
return {
color: "green",
};
},
而自定义指令中需要怎么接收这个值呢?
在声明自定义指令时,可以通过形参中的第二个参数 binding,来接收指令的参数值:
directives: {
color: {
// binding 是一个对象,该对象的 value 就是传递过来的值
bind(el, binding) {
el.style.color = binding.value;
},
},
},
但是这里有一点要强调,
<h3 v-color="color">Right 组件</h3>
像这句代码,里面接收不是 color 字符串,而是 color 在数据中所代表的值。
所以我们如果要自定义为红色,应该这样写:
<h3 v-color="'red'">Right 组件</h3>
3.5 update 函数
我们定义一个按钮,按了之后可以使得 Right 组件字样从绿色变成红色,但我们发现,只有数据变了,Right 组件这几个字依旧为绿色。
<template>
<div class="right-container">
<h3 v-color="color">Right 组件</h3>
<button @click="color = 'red'">变红</button>
<h3>Right 组件底部</h3>
</div>
</template>
<script>
export default {
data() {
return {
color: "green",
};
},
directives: {
color: {
// binding 是一个对象,该对象的 value 就是传递过来的值
bind(el, binding) {
el.style.color = binding.value;
},
},
},
}
</script>
这是因为bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。
update 函数会在每次 DOM 更新时被调用。示例代码如下:
directives: {
color: {
// 第一次绑定元素被调用
bind(el, binding) {
console.log(binding);
el.style.color = binding.value;
},
// 每次 DOM 更新被调用
update(el, binding) {
console.log(binding);
el.style.color = binding.value;
},
},
},
3.6 函数简写
如果 bind 和 update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:
directives: {
// 函数名就为自定义指令名,其他与 update 和 bind 函数逻辑一致
color(el, binding) {
console.log(binding);
el.style.color = binding.value;
},
},
3.7 全局自定义指令
全局共享的自定义指令需要通过 Vue.directive() 进行声明,示例代码在 main.js 里写:
Vue.directive('color', function (el, binding) {
el.style.color = binding.value;
})
如有错误,敬请指正,欢迎交流🤝,谢谢♪(・ω・)ノ