Vue3中的自定义指令directive是一种扩展HTML标签能力的机制。通过自定义指令,开发者可以直接在模板中使用指令名,并为其提供相应的逻辑。这些指令在Vue应用程序中扮演着重要角色,允许开发者执行一些低级DOM操作或访问元素状态。
自定义指令的钩子函数
自定义指令可以包含多个钩子函数,它们在指令的不同生命周期阶段被调用:
beforeMount(el, binding, vnode, prevVnode)
: 在元素被插入到 DOM 之前调用。mounted(el, binding, vnode, prevVnode)
: 在元素被插入到 DOM 后调用。beforeUpdate(el, binding, vnode, prevVnode)
: 在元素更新之前调用。updated(el, binding, vnode, prevVnode)
: 在元素更新后调用。beforeUnmount(el, binding, vnode, prevVnode)
: 在元素被移除之前调用。unmounted(el, binding, vnode, prevVnode)
: 在元素被移除后调用。
这些钩子函数都接收相同的参数:
el
: 指令所绑定的元素,可以用来直接操作 DOM。binding
: 一个对象,包含以下属性:
name
: 指令名,不包括v-
前缀。value
: 指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
: 指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
: 字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
: 传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
: 一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。vnode
: Vue 虚拟节点。prevVnode
: 上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
使用方法
1. 定义自定义指令
直接定义自定义指令,并通过 defineDirective
函数将其注册为局部指令。
<template>
<input v-focus />
</template>
<script setup lang="ts">
import { defineDirective } from 'vue'
// 定义一个名为 'focus' 的自定义指令
const focusDirective = defineDirective({
// 当元素被插入到 DOM 中时调用
mounted(el: HTMLElement) {
el.focus()
},
// 当元素更新时调用
updated(el: HTMLElement) {
// 这里可以添加更新时的逻辑,比如重新聚焦等
}
})
// 注册局部自定义指令
defineDirective('focus', focusDirective)
</script>
在上面的代码中,我们首先通过 import { defineDirective } from 'vue' 导入了 defineDirective 函数。然后,我们定义了一个名为 focusDirective 的对象,它包含了自定义指令的钩子函数。最后,我们使用 defineDirective 函数将 focusDirective 注册为名为 focus 的局部自定义指令。
2. 使用自定义指令
<template>
<input v-focus placeholder="这个输入框会自动聚焦" />
</template>
3. 带参数的自定义指令
<template>
<div v-scroll-to="'top'">滚动到顶部</div>
<div v-scroll-to="'bottom'">滚动到底部</div>
</template>
<script setup lang="ts">
import { defineDirective, onMounted, onUpdated, ref } from 'vue'
const scrollToDirective = defineDirective({
mounted(el, binding) {
const scrollTarget
使用场景
我们在实际项目中,会遇到各种场景,以下是典型场景:
- 焦点管理:自动设置输入框的焦点。
- 元素拖拽:使元素可以拖拽。
- 图片懒加载:延迟加载图片直到它们进入视口。
- 文本高亮:根据条件高亮文本中的特定部分。
- 权限控制:基于用户权限控制元素的显示或隐藏。
焦点管理已经在上述例子中讲解过;
1. 元素拖拽
<template>
<div id="app">
<div v-drag>Drag me</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
// 自定义指令 v-drag
const dragDirective = {
mounted(el: HTMLElement) {
let offsetX = 0;
let offsetY = 0;
let isDragging = false;
const handleMousedown = (event: MouseEvent) => {
offsetX = event.clientX - el.offsetLeft;
offsetY = event.clientY - el.offsetTop;
isDragging = true;
document.addEventListener('mousemove', handleMousemove);
document.addEventListener('mouseup', handleMouseup);
};
const handleMousemove = (event: MouseEvent) => {
if (!isDragging) return;
const x = event.clientX - offsetX;
const y = event.clientY - offsetY;
el.style.transform = `translate(${x}px, ${y}px)`;
};
const handleMouseup = () => {
isDragging = false;
document.removeEventListener('mousemove', handleMousemove);
document.removeEventListener('mouseup', handleMouseup);
};
el.addEventListener('mousedown', handleMousedown);
},
unmounted() {
// 清理事件监听器
document.removeEventListener('mousemove', handleMousemove);
document.removeEventListener('mouseup', handleMouseup);
}
};
// 注册自定义指令
const drag = directive(dragDirective);
</script>
<style scoped>
#app {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
div {
width: 100px;
height: 100px;
background-color: skyblue;
cursor: move;
user-select: none; /* 禁止文本选择 */
}
</style>
2. 图片懒加载
<template>
<div id="app">
<img v-lazy="imageSrc" alt="Lazy loaded image">
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
// 自定义指令 v-lazy
const lazyDirective = {
mounted(el: HTMLImageElement, binding: any) {
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 当图片进入视口时,设置图片的src属性
el.src = binding.value;
// 停止观察
observer.unobserve(el);
}
});
},
{ threshold: 0.1 } // 提前加载,可以设置阈值
);
observer.observe(el);
},
unmounted() {
// 组件卸载时,停止观察
observer.unobserve(el);
}
};
// 注册自定义指令
const lazy = directive(lazyDirective);
const imageSrc = ref('path/to/your/image.jpg'); // 图片地址
</script>
<style scoped>
#app {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
img {
width: 300px;
height: 200px;
}
</style>
3. 文本高亮
<template>
<div id="app">
<p v-highlight="'Vue 3'">Vue 3 is awesome!</p>
</div>
</template>
<script setup lang="ts">
import { Directive } from 'vue';
// 自定义指令 v-highlight
const highlightDirective: Directive = {
// 当绑定元素插入到 DOM 中。
mounted(el, binding) {
// 获取绑定值,即要高亮显示的文本
const searchTerm = binding.value.toString();
// 创建一个正则表达式,用于全局不区分大小写的匹配
const regex = new RegExp(searchTerm, 'gi');
// 获取元素的文本内容
const textContent = el.textContent || '';
// 使用replace方法替换匹配到的文本,并添加高亮样式
const highlightedText = textContent.replace(regex, (matchedText) => {
return `<mark>${matchedText}</mark>`;
});
// 设置元素的innerHTML为高亮后的文本
el.innerHTML = highlightedText;
},
// 当绑定元素更新时
updated(el, binding) {
// 更新文本高亮
this.mounted(el, binding);
}
};
// 注册自定义指令
const highlight = directive(highlightDirective);
</script>
<style scoped>
#app {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
mark {
background-color: yellow;
}
</style>
4. 权限控制
<template>
<div id="app">
<!-- 使用v-permission指令,参数为所需的权限 -->
<button v-permission="'admin'">Admin Only Button</button>
<button v-permission="'user'">User Button</button>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, Directive } from 'vue';
// 假设这是从后端或其他地方获取的当前用户的权限
const userPermissions = ref(['user']);
// 自定义指令 v-permission
const permissionDirective: Directive = {
mounted(el, binding) {
// 获取指令的绑定值,即所需的权限
const requiredPermission = binding.value;
// 检查用户是否有权限
if (!userPermissions.value.includes(requiredPermission)) {
// 如果没有权限,隐藏元素
el.style.display = 'none';
}
},
updated(el, binding) {
// 当指令的值更新时,重新检查权限
this.mounted(el, binding);
}
};
// 注册自定义指令
const permission = directive(permissionDirective);
// 示例:模拟从后端获取用户权限
onMounted(() => {
// 假设这是从后端API或其他地方获取权限的模拟
// 在实际应用中,你会在用户登录或其他时机获取这些权限
setTimeout(() => {
// 假设当前用户只有'user'权限
userPermissions.value = ['user'];
}, 1000);
});
</script>
<style scoped>
#app {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>