如何实现一个v-*指令
在vue.js中,我们经常使用v-for、v-if、v-bind、v-model等指令。
指令是如何实现的?
指令基本概念
Vue指令是一种特殊的标记,用于在模板中对DOM元素进行底层操作。指令以 v- 前缀标识,如 v-if、v-show、v-bind 等。
指令实现原理
- 编译阶段:Vue模板编译器会识别模板中的指令语法
- 解析阶段:将指令表达式解析为对应的处理函数
- 生命周期绑定:将指令逻辑绑定到组件的生命周期中
- DOM操作:在适当的时机对DOM元素进行操作
指令核心机制
- 指令本质上是对DOM操作的封装
- 通过生命周期钩子函数控制指令的行为时机
- 支持动态参数、修饰符和表达式求值
自定义指令实现要素
- 遵循Vue指令的生命周期规范
- 正确处理指令参数和修饰符
- 在合适的时机进行DOM操作和清理工作
实现一个v-loading指令
功能:需传递一个boolean值,当值为true时,显示loading效果,false则隐藏。
- 创建一个对象,用于存放指令逻辑。
ts
const vLoading = {}- 指令一般操作dom元素,所以要在页面挂载后执行。
ts
const vLoading = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
// el为指令绑定的元素,binding为指令的参数和修饰符
const loadingEl = document.createElement('div');
loadingEl.className = 'loading';
loadingEl.innerText = '加载中...';
el.style.position = 'relative';
loadingEl.style = `
position: absolute;
inset:0;
color:#fff;
display:flex;
justify-content: center;
align-items: center;
background-color: rgba(0,0,0,0.5);
`;
if (binding.value) {
el.appendChild(loadingEl);
} else {
el.removeChild(loadingEl);
}
}
}- 状态,当依赖数据更新时,指令也要更新,否则指令就无意义
ts
const vLoading = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const loadingEl = document.createElement('div');
loadingEl.className = 'loading';
loadingEl.setAttribute("data-loading", "true")
loadingEl.innerText = '加载中...';
el.style.position = 'relative';
loadingEl.style = `
position: absolute;
inset:0;
color:#fff;
display:flex;
justify-content: center;
align-items: center;
background-color: rgba(0,0,0,0.5);
`;
if (binding.value) {
el.appendChild(loadingEl);
} else {
el.removeChild(loadingEl);
}
},
updated(el: HTMLElement, binding: DirectiveBinding) {
if (binding.value === binding.oldValue) return
const loadingEl = document.querySelector('.loading')!;
if (binding.value) {
el.appendChild(loadingEl);
} else {
el.removeChild(loadingEl);
}
}
}完整测试代码
vue
<script setup lang="ts">
import {type DirectiveBinding, ref} from 'vue';
const loading = ref(true);
const vLoading = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const loadingEl = document.createElement('div');
loadingEl.className = 'loading';
loadingEl.innerText = '加载中...';
el.style.position = 'relative';
loadingEl.style = `
position: absolute;
inset:0;
color:#fff;
display:flex;
justify-content: center;
align-items: center;
background-color: rgba(0,0,0,0.5);
`;
if (binding.value) {
el.appendChild(loadingEl);
} else {
el.removeChild(loadingEl);
}
},
updated(el: HTMLElement, binding: DirectiveBinding) {
if (binding.value === binding.oldValue) return
const loadingEl = document.querySelector('.loading')!;
if (binding.value) {
el.appendChild(loadingEl);
} else {
el.removeChild(loadingEl);
}
}
}
setTimeout(() => {
loading.value = false;
}, 3000)
</script>
<template>
<div class="box" v-loading="loading"></div>
</template>
<style scoped>
body {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.box {
width: 400px;
height: 400px;
background-color: red;
}
</style>全局使用
在main.ts中全局注册
ts
const vLoading: Directive<HTMLElement, boolean> = {
mounted(el, binding) { /* 同上 */
},
updated(el, binding) { /* 同上 */
}
};
app.directive('loading', vLoading);钩子函数
directive也是有钩子函数的,可在特定时期执行操作。
| 钩子函数 | 作用 |
|---|---|
| beforeMount | 在指令首次绑定到元素且未挂载到DOM之前调用 |
| mounted | 被绑定元素挂载到DOM之时调用,可访问元素实例,但DOM节点还未更新 |
| beforeUpdate | 在指令所在组件的 VNode 更新之前调用 |
| updated | 在指令所在组件的 VNode 及其子组件的 VNode 更新之后调用 |
| beforeUnmount | 在指令所绑定的元素/组件/父组件被卸载之前调用 |
| unmounted | 在指令所绑定的元素/组件/父组件被卸载之时调用 |
- VNode:Vue中的虚拟DOM对象,用于描述真实DOM结构。
binding对象所有属性
| 属性 | 描述 |
|---|---|
| instance | 指令所绑定的组件实例 |
| value | 指令的绑定值 |
| oldValue | 指令绑定的值的旧值 |
| arg | 指令的参数,如v-bind:arg |
| modifiers | 指令的修饰符,如.stop |