一、内置指令
1、v-memo
该指令是Vue3的v3.2
版本之后新增的指令,用于实现组件模板缓存,优化组件更新时的性能。该指令接收一个固定长度的依赖值数组,在组件进行更新渲染时,如果数组中的每个依赖值都与上一次渲染时的值相同,则指令所在的模板元素及其子元素的更新渲染将会被跳过,甚至在创建虚拟DOM的阶段也会跳过这些节点的创建。只有当依赖值发生变化时,指令所在的模板元素及其子元素才会进行更新渲染。
<template>
<div>
<div v-memo="[testA,testB]">
<h4>验证v-memo指令的作用</h4>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const testA = ref('testA')
const testB = ref('testB')
</script>
如果v-memo
指令接收的是一个空数组[]
,则其作用与v-once
效果相同,仅在初始阶段渲染模板元素一次,在后续的更新渲染中,模板元素及其所有子元素将被当作静态内容并跳过更新渲染。
与v-for一起使用:
v-memo
指令最常见的使用场景是与v-for
渲染的长列表结合使用,例如:当组件的selected状态发生变化时,默认在更新创建虚拟DOM时会重复创建大量的的vnode
,进行diff
对比更新。当我们使用v-memo="[item.id === selected]"
时,表示只有当前元素的selected
状态发生变化时,才会更新当前元素,避免了大量无用vnode
的创建。
<!-- [item.id === selected] 表示只有当前元素的selected状态发生变化时 才会更新当前元素 -->
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
<p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
<p>...more child nodes</p>
</div>
2、v-for和v-if
在Vue2中v-for
和v-if
不允许用在同一元素上,因为两者没有一个明确的优先级。在Vue3中虽然不推荐同时使用v-for
和v-if
,但两者可以同时使用。
当两者作用于同一元素时,v-if
的优先级高,首先被执行,且v-if
无法访问到v-for
中的数据。因此还是建议将两者放在不同元素上使用:
<ul>
<!-- 此时会报错 因为 item 是v-for中的属性 v-if中无法访问 -->
<li v-for="item in list" v-if="item === 'aaa'">{{ item }}</li>
</ul>
<!-- 建议使用以下写法 -->
<ul>
<template v-for="item in list">
<li v-if="item === 'aaa'">{{ item }}</li>
</template>
</ul>
3、其余指令
其余指令在Vue3中的功能与Vue2中功能相同,并未更新,此处就不展开讲述了。需要翻阅的可以查看我之前发布的内容:Vue2学习笔记。
二、自定义指令
1、基础用法
除了Vue为我们提供的内置指令之外,还允许注册自定义指令,实现逻辑复用。但不建议过多使用自定义指令,只有当功能需要直接操作 DOM 时,才应该使用自定义指令。
一个自定义指令由一个包含指令钩子函数的对象来定义。在 <script setup>
中,任何以 v
开头的驼峰式命名的变量都可以被用作一个自定义指令,例如:vFocus
可以在模板中以v-focus
指令的形式使用:
<template>
<div>
<!-- 在模板元素上使用自定义指令 -->
<input type="text" v-focus />
</div>
</template>
<script setup lang="ts">
// 声明一个自定义指令 作用为控制元素自动获取焦点
const vFocus = {
mounted: (el) => el.focus()
}
</script>
在 <script setup>
中声明的自定义指令,只能在当前组件实例内使用。如果需要,也可以在mian.js
中通过应用实例将自定义指令注册为全局指令,可在应用全局范围内使用:
// mian.js 文件
import { createApp } from 'vue'
import App from './App.vue'
// 创建应用实例
const app = createApp(App)
// 声明一个自定义指令 作用为控制元素自动获取焦点
const vFocus = {
mounted: (el) => el.focus()
}
// 注册全局的自定义指令
app.directive('focus', vFocus)
2、在组件上使用自定义指令
当自定义指令应用在组件上时,如果子组件只有一个根节点,则指令会被透传到子组件的根节点上;如果子组件含有多个根节点,自定义指令将会被忽略,同时会抛出一个警告。而且自定义指令不能通过$attrs
对象传递给某个元素。
不建议在组件上使用自定义指令。
3、指令钩子函数
一个自定义指令对象中的钩子函数有七个可选的钩子函数,分别为:created()
、beforeMount()
、mounted()
、beforeUpdate()
、updated()
、beforeUnmount()
、unmounted()
:
const myDirective = {
// 该钩子函数在绑定元素属性或事件监听器应用之前被调用
created(el, binding, vnode, prevVnode) {},
// 该钩子函数在绑定元素被挂载到页面之前被调用
beforeMount(el, binding, vnode, prevVnode) {},
// 该钩子函数在绑定元素及其后代节点挂载到页面之后被调用
mounted(el, binding, vnode, prevVnode) {},
// 该钩子函数在绑定元素更新前被调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 该钩子函数在绑定元素更新完成后调用
updated(el, binding, vnode, prevVnode) {},
// 该钩子函数在绑定元素从页面卸载之前被调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 该钩子函数在帮顶元素从页面卸载之后被调用
unmounted(el, binding, vnode, prevVnode) {}
}
每个钩子函数都接收四个参数,其中除了参数el
之外,其余元素都是只读的。
参数el
表示指令绑定的DOM元素,可用于直接进行DOM操作,例如:
mounted: (el) => el.focus()
参数binding
是一个对象,其中包含以下属性:
value
:指令接收的属性值。oldValue
:元素更新之前指令接收的属性值,仅在beforeUpdate()
、updated()
钩子函数中可用。arg
:指令后面跟随的参数,元素以v-指令:参数
的形式设置参数。modifiers
:对象结构,对象中存储了指令的修饰符,以{修饰符名称: true}
的形式存储。instance
:指令所在的组件实例。dir
:指令的定义对象。
参数vnode
表示绑定元素对应的虚拟DOM。
参数prevNode
表示元素更新前所对应的虚拟DOM,仅在beforeUpdate()
、updated()
钩子函数中可用。
以update()
钩子函数为例:
<template>
<div>
<!-- 在模板元素上使用自定义指令 -->
<input type="text" v-focus:foo.aaa="'ccc'" />
</div>
</template>
<script setup>
// 声明一个自定义指令 作用为控制元素自动获取焦点
const vFocus = {
// 该钩子函数在绑定元素更新完成后调用
updated(el, binding, vnode, prevVnode) {
console.log('updated---el---',el);
console.log('updated---binding---',binding);
console.log('updated---vnode---',vnode);
console.log('updated---prevVnode---',prevVnode);
},
}
</script>
控制台输出结果:
4、简写形式
如果一个自定义指令,只需要在mounted()
和updated()
钩子函数中执行相同的代码,无需其他钩子函数参与,那么此时可以直接用一个函数来声明该自定义指令:
<template>
<div>
<!-- 在模板元素上使用自定义指令 -->
<p v-color="'red'">自定义指令简写</p>
</div>
</template>
<script setup lang="ts">
// 利用简写形式 声明一个自定义指令 作用为改变元素的字体颜色
const vColor = (el,binding) => {
// 该函数会在 mounted 和 updated 时调用
el.style.color = binding.value;
}
</script>
5、对象字面量属性值
如果一个自定义指令需要接收多个属性值,则可以通过对象字面量的形式来传递属性值:
<template>
<div>
<p v-color="{ color: 'red', bgColor: 'green' }">自定义指令简写</p>
</div>
</template>
<script setup lang="ts">
// 利用简写形式 声明一个自定义指令 作用为改变元素的字体颜色
const vColor = (el,binding) => {
el.style.color = binding.value.color;
el.style.backgroundColor = binding.value.bgColor;
}
</script>
三、插件
1、简介
插件(Plugins
)是Vue中一种重要的扩展机制,允许开发者向 Vue 应用示例添加全局级别的功能。但插件本身并不具有添加全局功能的能力,而是需要借助app.component()
等方法去实现功能。所以在我看来插件更像是一个将main.js
中功能抽出集合成的模块,然后通过导入模块,实现相应的功能,避免在main.js
文件中书写过多的逻辑代码。
插件常见的应用场景主要包括:
- 引用第三方库,例如vue-router、ElementPlus等等。
- 结合
app.component()
注册全局组件。 - 结合
app.directive()
注册全局自定义指令。 - 结合
app.provide()
提供全局依赖。 - 结合
app.config.globalProperties
添加全局属性或全局方法。
2、基本用法
插件可以是一个拥有install()
安装方法的对象,也可以直接是一个安装方法本身。插件的安装方法接收两个参数:一个app
表示插件安装到的Vue应用实例对象,一个options
表示插件安装时的可选配置项:
const myPlugin = {
install(app, options) {
// app 是插件要要安装的应用实例
// options 是插件安装时的可选配置项
}
}
在安装插件时,只需要将插件所在js
文件导入到mian.js
中,然后通过app.use()
方法进行安装即可:
import { createApp } from 'vue'
// 导入插件
import myPlugin from './plugins/myPlugin.js'
const app = createApp({})
// 安装插件
app.use(myPlugin, {
/* 可选的选项 这里面的内容会传递给插件install()方法 的 options 参数 */
})
3、自定义插件
创建插件:
首先创建一个插件的js
文件,内部书写插件的具体功能逻辑代码:
// logPlugin.js
const LogPlugin = {
// 插件的安装方法
install(app, options) {
// 通过 provide() 方法传递全局方法
app.provide('log', function (message) {
// 如果传入了options.prefix,则在打印message时加上前缀
if (options && options.prefix) {
console.log(`${options.prefix}: ${message}`);
} else {
// 否则直接打印message
console.log(message);
}
});
}
}
// 导出插件
export default LogPlugin;
安装插件:
在main.js
导入插件文件,并通过app.use()
进行安装:
import { createApp } from 'vue'
import App from './App.vue'
import LogPlugin from './plugins/logPlugin'
// 创建应用实例
const app = createApp(App)
// 将插件安装到应用示例上
app.use(LogPlugin, { prefix: '小助手提示您' })
app.mount('#app')
使用插件:
在组件中需要通过inject()
接收插件提供的全局方法:
<script setup>
import { inject } from 'vue';
const log = inject('log');
log('使用自定义插件的方法')
// 其他场景使用
</script>s