文章目录
一、使用场景
只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用自定义指令。
一个常见例子是使元素获取焦点的 v-focus 指令。该指令比 autofocus 属性更有用,因为它不仅在页面加载时有效,而且在 Vue 动态插入元素时也有效!
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
二、注册
局部注册
组合式
<script setup>
// 在模板中启用 v-highlight
const vHighlight = {
mounted: (el) => {
el.classList.add('is-highlight')
}
}
</script>
<template>
<p v-highlight>This sentence is important!</p>
</template>
选项式
export default {
setup() {
/*...*/
},
directives: {
// 在模板中启用 v-focus
focus: {
/* ... */
}
}
}
全局注册
const app = createApp({})
// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})
三、钩子函数
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {}
}
四、钩子函数参数
指令的钩子会传递以下几种参数:
- el:指令绑定到的元素。这可以用于直接操作 DOM。
- binding:一个对象,包含以下属性。
- value:传递给指令的值。例如在 v-my-directive=“1 + 1” 中,值是 2。
- oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
- arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 “foo”。
- modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
- instance:使用该指令的组件实例。
- dir:指令的定义对象。
- vnode:代表绑定元素的底层 VNode。
- prevVnode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。
❗❗❗除了 el 外,其他参数都是只读的,不要更改它们。若你需要在不同的钩子间共享信息,推荐通过元素的 dataset attribute 实现。
五、应用示例
1、按钮权限验证
功能描述: 项目根据登录用户所配置的角色权限,对按钮进行权限控制,拥有权限的按钮才能够触发点击事件,没有权限的按钮触发时进行提示。
/**
* # 验证用户权限, 并进行提示
*
* @example
* ```vue
* <el-button
* v-auth="{
* code: AuthCode.UPLOAD_FILE,
* event: handleUploadConfirm,
* }"
* >
* 确认上传
* </el-button>
* ```
*
* @param code 权限 code
*/
export default <Directive>{
mounted(el: HTMLElement, bindings) {
if (!bindings.value) {
throw new Error('value 不能为空')
}
if (typeof bindings.value !== 'object') {
throw new Error('value 必须为对象')
}
const { code, event } = bindings.value
el.addEventListener(bindEvent, () => {
if (!checkAuth(code)) {
return
}
if (typeof event === 'function') {
event()
}
})
}
}
/**
* @param code 权限 code
* @returns
*/
function checkAuth(code: string) {
...
}
2、自定义用户行为收集指令
功能描述:需要对项目中用户的行为进行收集,以备后续的日志记录查询或者用户行为分析。 比如记录哪个用户,在什么时间,在哪个页面,触发了什么功能。支持按钮按钮行为收集、非按钮点击行为收集。在项目入口写定时器,每隔一定时间调接口将用户行为存到数据库。
/**
* @example
* <el-link
* type="primary"
* v-collect
* @click="getRoleAuthList(row)"
* >
* 查看
* </el-link>
*
* <el-button
* size="large"
* type="primary"
* v-collect
* @click="handleClickMove"
* >
* 新增项目
* </el-button>
*
* 自定义用户行为收集指令
*/
export default <Directive>{
mounted(el: HTMLElement, bindings) {
el.addEventListener('mousedown', e => {
e.stopPropagation()
const collect = getCollect()
collect.push({
...
})
setCollect(collect)
})
}
}
// /utils/collect.ts
const collectKey = 'USER_SELECT'
function getCollect(): Collect[] {
const collect = window.localStorage.getItem(collectKey) || '[]'
return JSON.parse(collect)
}
function setCollect(collect: Collect[]) {
window.localStorage.setItem(collectKey, JSON.stringify(collect))
}
3、按钮点击防抖
功能描述:点击按钮进行操作时,防止用户短时间点击多次触发多次事件,为按钮点击事件添加防抖功能,默认 1 秒以内重复点击无效。实现方案是点击按钮触发一次事件,给按钮设置 disabaled 属性为 true,1 秒后删除该限制。
/**
* @example
* <el-button v-clicked:500></el-button>
*
* @param delay number类型, 设置禁止点击的时间间隔, 默认1000
*/
export default {
mounted(el: HTMLElement | HTMLButtonElement, bindings: DirectiveBinding) {
let timer: number | null = null
let delay = 1000
const oldEvents = el.style.pointerEvents
if (bindings.arg) {
if (!/^\d+$/.test(bindings.arg)) {
throw new Error('参数必须是数字')
}
delay = Number(bindings.arg)
}
el.addEventListener('click', setDisabled)
function setDisabled() {
el.setAttribute('disabled', 'true')
// el.classList.add('is-disabled')
if (el.tagName !== 'BUTTON') {
el.style.pointerEvents = 'none' // 点击不到按钮,穿透到下层元素
}
if (timer) {
window.clearTimeout(timer)
timer = null
}
timer = window.setTimeout(() => {
// el.classList.remove('is-disabled')
el.removeAttribute('disabled')
el.style.pointerEvents = oldEvents
}, delay)
}
}
}
4、输入框自动获取焦点
功能描述:页面加载之后自动聚焦到某个输入框,使输入框处于输入状态
/**
* @example
* <el-input v-focus></el-input>
*/
export default {
mounted(el: HTMLElement | HTMLInputElement) {
let ipt: HTMLInputElement | null
if (el.nodeName === 'INPUT') {
ipt = el as HTMLInputElement
} else {
ipt = el.querySelector('.el-input__inner') as HTMLInputElement
}
ipt && (ipt as HTMLInputElement).focus()
}
}
5、输入框自动去空字符串
interface HTMLElementPlus extends HTMLElement {
_handler: (e: KeyboardEvent) => void
_ele: HTMLElementPlus
}
export default {
mounted(el: HTMLElementPlus) {
let ipt
if (el.nodeName === 'INPUT') {
ipt = el
} else {
ipt = el.querySelector('.el-input__inner')
}
const handler = (e: KeyboardEvent) => {
if (e.code === 'Space') {
e.preventDefault()
}
}
el._ele = ipt as HTMLElementPlus
el._handler = handler
;(ipt as HTMLElementPlus).addEventListener('keydown', handler)
},
unmounted(el: HTMLElementPlus) {
const { _ele } = el
_ele.removeEventListener('keydown', _ele._handler)
}
}