vue指令的实现
Vue 指令的实现原理
Vue 指令是带有 v- 前缀的特殊属性,用于在 DOM 元素上应用响应式行为。指令的核心实现依赖于 Vue 的编译系统和响应式系统。
指令的注册
指令通过 Vue.directive 方法全局注册,或在组件选项中通过 directives 局部注册。注册时需要提供指令定义对象,包含钩子函数如 bind、inserted、update 等。
Vue.directive('focus', {
inserted: function (el) {
el.focus()
}
})
编译阶段
模板编译时,解析器会识别 v- 开头的属性,将其转换为指令的 AST 节点。代码生成阶段会为指令生成对应的渲染函数代码,包含指令的初始化逻辑。
指令钩子调用
当虚拟 DOM 渲染和更新时,会调用指令的钩子函数:
bind:只调用一次,指令第一次绑定到元素时调用。inserted:被绑定元素插入父节点时调用。update:所在组件的 VNode 更新时调用,可能在其子 VNode 更新之前。componentUpdated:所在组件及子组件的 VNode 全部更新后调用。unbind:指令与元素解绑时调用。
内置指令的实现示例
以 v-model 为例,其本质是语法糖,根据元素类型自动扩展为不同的属性和事件:
- 文本输入框:绑定
value属性和input事件。 - 复选框:绑定
checked属性和change事件。 - 下拉框:绑定
value属性和change事件。
// 简化版的 v-model 实现逻辑
function model(node, dir, _warn) {
const value = dir.value
const modifiers = dir.modifiers
const tag = node.tag
const type = node.attrsMap.type
if (tag === 'input' && type === 'checkbox') {
addHandler(node, 'change', `$event.target.checked?${value}=true:${value}=false`)
} else if (tag === 'input' || tag === 'textarea') {
addHandler(node, 'input', `${value}=$event.target.value`)
}
}
自定义指令的实现步骤
定义指令对象
指令对象包含生命周期钩子,接收以下参数:
el:指令绑定的 DOM 元素。binding:包含指令信息的对象(如value、oldValue、arg、modifiers)。vnode:Vue 编译生成的虚拟节点。oldVnode:上一个虚拟节点。
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
const s = binding.arg || 'top'
el.style[s] = binding.value + 'px'
}
})
使用动态参数
指令的参数可以是动态的,通过方括号语法实现:
<div v-pin:[direction]="200"></div>
data() {
return {
direction: 'top'
}
}
处理指令更新
当指令的值或参数变化时,update 钩子会被调用:
Vue.directive('pin', {
update: function (el, binding) {
const s = binding.arg || 'top'
el.style[s] = binding.value + 'px'
}
})
指令的高级用法
对象字面量
指令的值可以接收对象字面量,便于传递多个参数:
<div v-demo="{ color: 'white', text: 'hello' }"></div>
Vue.directive('demo', {
bind: function (el, binding) {
console.log(binding.value.color) // "white"
console.log(binding.value.text) // "hello"
}
})
函数简写
如果指令只需要 bind 和 update 钩子,且逻辑相同,可以使用函数简写:
Vue.directive('color', function (el, binding) {
el.style.color = binding.value
})
指令与组件通信
指令可以通过 vnode.context 访问组件实例,调用其方法或修改数据:
Vue.directive('notify', {
bind: function (el, binding, vnode) {
el.addEventListener('click', () => {
vnode.context.$emit('notified', binding.value)
})
}
})






