VUE ---- 自定义指令的理解和应用
前段时间在项目当中有一个需求要操作到DOM层面。尽管Vue框架推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。考虑到上面提到的业务场景,Vue官方提供了自定义指令。因为指令的颗粒度比较细能够直击要害(DOM),而且具备有高度的灵活性,运用起来非常的方便。
一、概念
Vue中内置了很多的指令,如v-model、v-show、v-html等,但是有时候这些指令并不能满足我们,或者说我们想为底层元素(DOM)附加一些特别的功能,这时候,我们就需要用到vue中一个很强大的功能了—自定义指令(Vue.directive)。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。
这里敲黑板了!重要的事情上面已经说三遍了!自定义指令解决的问题或者说使用场景是对普通 DOM 元素进行底层操作,所以我们不能盲目的胡乱的使用自定义指令。
二、使用方式
tips:除了el对象之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行,以避免在开发过程中带来不必要的麻烦。
a. Vue2.0:
// 1.注册全局自定义指令
Vue.directive('xxx', {
// 指令第一次绑定到元素时「初始化」……
bind(el, binding, vnode, oldVnode) {
// el:绑定指令的DOM元素对象
// binding:数据对象
// + name:不带“v-”的指令名字
// + rawName:带“v-”的指令名字「含值和修饰符」
// + value:指令绑定的值 v-xxx="1+1" -> value:2
// + expression:指令表达式 v-xxx="1+1" -> expression:"1+1"
// + arg:传给指令的参数 v-xxx:n -> arg:"n"
// + modifiers:修饰符对象 v-xxx.stop.finish -> modifiers:{stop:true,finish:true}
// vnode:Vue编译生成的虚拟节点
// oldVnode:上一个虚拟节点
},
// 被绑定元素插入父节点时「父节点不一定非要插入到文档中,一般是插入到DOM中」……
inserted() {},
// 所在组件的VNode更新时「指令的值可能发生了改变,也可能没有」……
update(el, binding, vnode, oldVnode) {
// binding:数据对象
// + oldArg
// + oldValue
// + ...
},
// 所在组件的VNode及其子VNode全部更新后……
componentUpdated() {},
// 指令与元素解绑时……
unbind() {}
});
// or
// 2.注册局部自定义指令(组件内)
directives:{
xxx:{
bind(el, binding, vnode, oldVnode) {},
inserted() {},
update(el, binding, vnode, oldVnode) {},
unbind() {}
}
}
i:列举官方实例
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'rawName:' + s(binding.rawName) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
// name: "demo" 不带“v-”的指令名字
// rawName: "v-demo" 带“v-”的指令名字「含值和修饰符」
// value: "hello!" 指令绑定的值 v-demo="message" -> value:hello!
// expression: "message" 指令表达式 v-demo="message" -> expression:"message"
// argument: "foo" 传给指令的参数 v-demo:foo -> arg:"foo"
// modifiers: {"a":true, "b":true} 修饰符对象 v-demo.a.b -> modifiers:{a:true,b:true}
// vnode keys: Vue编译生成的虚拟节点 -> tag,data,children,text,elm,ns,context,fnContext,fnScopeId,key,componentOptions,componentInstance,parent,raw,isStatic,isRootInsert,isComment,isCloned,isOnce,asyncFactory,asyncMeta,isAsyncPlaceholder
ii:动态指令参数
<div id="dynamicexample">
<h3>Scroll down inside this section ↓</h3>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
var s = (binding.arg == 'left' ? 'left' : 'top') // s => left
el.style[s] = binding.value + 'px'
}
})
new Vue({
el: '#dynamicexample',
data: function () {
return {
direction: 'left'
}
}
})
iii:函数简写方式
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子的时候,就可以采取函数简写的方式:
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
iiii:对象字面量
如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
b. Vue3.0,和Vue2.0有了一些钩子的区别:
import {
createApp
} from 'vue';
const app = createApp(App);
// 1.注册全局自定义指令
app.directive('xxx', {
// 绑定元素之前
created() {},
// 指令首次绑定到元素且在安装父组件之前...「等同于bind」
beforeMount(el, binding, vnode, prevVnode) {
// binding:数据对象
// + instance: 使用指令的组件实例
// + arg:传给指令的参数 v-xxx:n -> arg:"n"
// + modifiers:修饰符对象 v-xxx.stop -> modifiers:{stop:true}
// + value:指令绑定的值 v-xxx="1+1" -> value:2
// + oldValue:之前绑定的值
// + dir: 一个对象,在注册指令时作为参数传递
},
// 安装绑定元素的父组件时...「等同于inserted」
mounted() {},
// 在包含组件的VNode更新之前...
beforeUpdate() {},
// 在包含组件的VNode及其子VNode更新后...「等同于componentUpdated」
updated() {},
// 在卸载绑定元素的父组件之前...
beforeUnmount() {},
// 指令与元素解除绑定且父组件已卸载时...「等同于unbind」
unmounted() {}
});
app.mount('#app');
// or
// 2.注册局部自定义指令(组件内)
directives:{
xxx:{
beforeMount(el, binding, vnode, prevVnode) {},
mounted() {},
beforeUpdate() {},
update() {},
beforeUnmount() {},
unmounted() {}
}
}
i:官方提供实例
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
const app = Vue.createApp({
data() {
return {
message: 'hello!'
}
}
})
app.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'value: ' + s(binding.value) + '<br>' +
'arg: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
app.mount('#hook-arguments-example')
// value: "hello!" 指令绑定的值 v-demo="message" -> value:hello!
// arg: "foo" 传给指令的参数 v-demo:foo -> arg:"foo"
// modifiers: {"a":true, "b":true} 修饰符对象 v-demo.a.b -> modifiers:{a:true,b:true}
// vnode keys: Vue编译生成的虚拟节点 -> tag,data,children,text,elm,ns,context,fnContext,fnScopeId,key,componentOptions,componentInstance,parent,raw,isStatic,isRootInsert,isComment,isCloned,isOnce,asyncFactory,asyncMeta,isAsyncPlaceholder
ii:动态指令参数
基本上和Vue2.0版本的例子一样,各位看官自行参考。
iii:函数简写方式
基本上和Vue2.0版本的例子一样,各位看官自行参考。
const app = Vue.createApp()
app.directive('pin', (el, binding) => {
el.style.position = 'fixed'
const s = binding.arg || 'top'
el.style[s] = binding.value + 'px'
})
iiii:对象字面量
这种情况用来应付接收多个指令值,记住,指令函数能够接受所有合法的 JavaScript 表达式。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
三、弊端
vue的自定义指令是一个比较容易引起内存泄漏的地方,原因就在于指令通常给元素绑定了事件,但是如果忘记了解绑,就会产生内存泄漏的问题。
所以在unbind也好在unmounted也好,都要针对绑定的事件进行消除,否则就会产生内存泄漏。