自定义指令
当内置指令无法满足需求时,你可以编写自定义指令。自定义指令适合封装直接操作 DOM 的逻辑,例如自动聚焦、权限控制、拖拽、懒加载等。
定义指令
使用 defineDirective 定义一个指令,需要提供指令名称和指令对象:
tsx
import { defineDirective } from 'vitarx'
defineDirective('focus', {
mounted(el, binding) {
if (binding.value) {
el.focus()
}
}
})- 第一个参数是指令名称,在 JSX 中使用时需要加上
v-前缀(如v-focus) - 第二个参数是指令对象,包含生命周期钩子函数
指令钩子
指令对象可以包含以下钩子函数:
| 钩子 | 调用时机 | 用途 |
|---|---|---|
created | 元素已创建时 | 初始化状态、设置副作用 |
mounted | 元素挂载到 DOM 后 | 操作 DOM、绑定事件 |
dispose | 元素即将被销毁 | 清理副作用、解绑事件 |
getSSRProps | 服务端渲染时 | 返回需要合并的属性对象 |
每个钩子函数接收三个参数:
typescript
(el: HostElement, binding: DirectiveBinding, view: ElementView) => void- el:指令绑定的 DOM 元素
- binding:指令绑定信息,包含
value(指令绑定的值)和可选的arg(指令参数) - view:视图节点实例
DirectiveBinding
typescript
interface DirectiveBinding {
/** 指令绑定的值 */
readonly value: any
/** 指令绑定的参数 */
readonly arg?: string
}在 JSX 中使用
定义好指令后,可以直接在 JSX 中通过 v-指令名 使用:
tsx
import { ref, createApp, defineDirective } from 'vitarx'
// 定义自动聚焦指令
defineDirective('focus', {
mounted(el, binding) {
if (binding.value) {
el.focus()
}
}
})
function App() {
const shouldFocus = ref(true)
return <input v-focus={shouldFocus} placeholder="自动聚焦" />
}
createApp(App).mount('#app')withDirectives — 手动绑定指令
除了在 JSX 中使用 v- 前缀语法,你还可以使用 withDirectives 函数手动为视图绑定指令。这在需要动态绑定指令的场景下很有用。
tsx
import { defineDirective, withDirectives, ref, createApp } from 'vitarx'
// 定义指令
defineDirective('color', {
mounted(el, binding) {
el.style.color = binding.value
}
})
function App() {
const color = ref('red')
// 使用 withDirectives 手动绑定指令
return withDirectives(<div>彩色文字</div>, [['color', { value: color }]])
}
createApp(App).mount('#app')withDirectives 接受两个参数:
- view:要添加指令的视图节点
- directives:指令数组,每个元素是一个元组
[name, binding]name可以是字符串(指令名称)或指令对象本身binding是DirectiveBinding对象,包含value和可选的arg
指令注册方式
指令有三种注册方式,对应不同的作用范围:
1. 全局注册
在组件外部调用 defineDirective,指令会被注册到全局缓存中,所有组件都可以使用:
tsx
import { defineDirective } from 'vitarx'
// 全局注册,所有组件可用
defineDirective('focus', {
mounted(el, binding) {
if (binding.value) el.focus()
}
})2. 组件局部注册
在组件函数内部调用 defineDirective,指令只会注册到当前组件的作用域中:
tsx
import { defineDirective, ref, createApp } from 'vitarx'
function App() {
// 组件局部注册,仅当前组件可用
defineDirective('highlight', {
mounted(el, binding) {
el.style.backgroundColor = binding.value
}
})
const color = ref('yellow')
return <span v-highlight={color}>高亮文本</span>
}
createApp(App).mount('#app')3. 应用级注册
通过 app.directive() 注册指令,指令在该应用实例的范围内可用:
tsx
import { createApp } from 'vitarx'
const app = createApp(App)
// 应用级注册,该应用下所有组件可用
app.directive('focus', {
mounted(el, binding) {
if (binding.value) el.focus()
}
})
app.mount('#app')app.directive() 也可以用来获取已注册的指令:
tsx
// 获取已注册的指令
const focusDirective = app.directive('focus')指令查找优先级
当多个作用域中存在同名指令时,按以下优先级查找:
- 组件局部指令 — 在组件内部通过
defineDirective注册的 - 应用级指令 — 通过
app.directive()注册的 - 全局指令 — 在组件外部通过
defineDirective注册的
resolveDirective — 解析指令
resolveDirective 根据指令名称查找对应的指令对象,查找顺序遵循上述优先级。名称中的 v- 前缀可以省略:
tsx
import { resolveDirective } from 'vitarx'
// 以下两种写法等价
const directive1 = resolveDirective('focus')
const directive2 = resolveDirective('v-focus')完整示例
自定义 focus 指令
下面是一个完整的自动聚焦指令示例,支持动态控制是否聚焦:
tsx
import { ref, createApp, defineDirective } from 'vitarx'
// 定义自动聚焦指令
defineDirective('focus', {
mounted(el, binding) {
// binding.value 控制是否聚焦
if (binding.value) {
el.focus()
}
}
})
function App() {
const autoFocus = ref(true)
return (
<div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
<h2>自动聚焦指令示例</h2>
<input v-focus={autoFocus} placeholder="我会自动聚焦" />
<div style={{ marginTop: '12px' }}>
<button
onClick={() => {
autoFocus.value = true
}}
>
启用聚焦
</button>
<button
onClick={() => {
autoFocus.value = false
}}
>
关闭聚焦
</button>
</div>
</div>
)
}
createApp(App).mount('#app')自定义权限指令
下面是一个权限控制指令的示例,没有权限时移除元素:
tsx
import { ref, createApp, defineDirective, type DirectiveBinding } from 'vitarx'
// 定义权限指令
defineDirective('permission', {
mounted(el, binding) {
// binding.value 是当前用户拥有的权限列表
// binding.arg 是该元素需要的权限标识
const userPermissions: string[] = binding.value || []
const requiredPermission = binding.arg
// 如果用户没有所需权限,移除元素
if (requiredPermission && !userPermissions.includes(requiredPermission)) {
el.parentNode?.removeChild(el)
}
}
})
function App() {
// 模拟当前用户的权限列表
const permissions = ref(['read', 'edit'])
return (
<div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
<h2>权限指令示例</h2>
<p>当前权限:{permissions.value.join(', ')}</p>
{/* 有 read 权限,显示 */}
<div v-permission={[permissions.value, 'read']} style={{ color: 'green' }}>
✅ 你有查看权限
</div>
{/* 有 edit 权限,显示 */}
<div v-permission={[permissions.value, 'edit']} style={{ color: 'green' }}>
✅ 你有编辑权限
</div>
{/* 没有 delete 权限,不显示 */}
<div v-permission={[permissions.value, 'delete']} style={{ color: 'red' }}>
❌ 你有删除权限
</div>
<div style={{ marginTop: '12px' }}>
<button
onClick={() => {
permissions.value = ['read', 'edit', 'delete']
}}
>
赋予全部权限
</button>
<button
onClick={() => {
permissions.value = ['read']
}}
>
仅保留查看权限
</button>
</div>
</div>
)
}
createApp(App).mount('#app')带副作用的指令
当指令中使用了响应式副作用(如 viewEffect),必须在 dispose 钩子中清理,避免内存泄漏:
tsx
import { ref, createApp, defineDirective, viewEffect } from 'vitarx'
// 定义一个跟随鼠标位置的指令
defineDirective('mouse-tracker', {
created(el, binding) {
// 创建响应式副作用,当 binding.value 变化时自动更新
const dispose = viewEffect(() => {
const enabled = binding.value
if (enabled) {
el.textContent = '鼠标追踪已开启'
el.style.cursor = 'pointer'
} else {
el.textContent = '鼠标追踪已关闭'
el.style.cursor = 'default'
}
})
// 保存清理函数,供 dispose 钩子使用
el._disposeTracker = dispose
},
dispose(el) {
// 清理副作用,防止内存泄漏
el._disposeTracker?.()
delete el._disposeTracker
}
})
function App() {
const enabled = ref(false)
return (
<div style={{ padding: '20px' }}>
<button
onClick={() => {
enabled.value = !enabled.value
}}
>
切换追踪
</button>
<div
v-mouse-tracker={enabled}
style={{ marginTop: '12px', padding: '12px', border: '1px solid #ccc' }}
/>
</div>
)
}
createApp(App).mount('#app')