关于自定义指令,事实上在做前端开发第一年时根本就没有任何的了解,第一次接触到这个概念还是在一次面试当中,我讲了一堆如何遍历标签的属性的原生方法,结果面试官直接回答:这不是vue自定义指令吗。。。233
嗯 那么开始进入主题。
vue功能默认内置的指令 (v-model ,v-show ,v-for 等等…)
那么自定义指令就是自己设置一个指令,比如聚焦。
// 注册一个全局自定义指令 v-focus
//全局注册
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
//组件注册
//如果想注册局部指令,组件中也接受一个 directives 的选项:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
//使用方式
<input v-focus>
到此,基本上我们就知道自定义指令的用法和作用了,但是我们不仅会有疑问,这个功能的使用场景在哪?那么带着这个问题我回想起面试时我也问过面试官这个问题,面试官回答:在使用组件时,你想对组件使用v-model咋办?
嗯…
面对这个问题,有两种解决方式,
一种从prototype原型链去复刻vue v-model的原理加在组件上。
而另一种便是使用自定义指令。
那么我们来尝试自定义指令的v-model效果吧。
先新建一个组件zidingyizujian.vue(注意:这里因为偷懒所以用拼音命名,实际开发不要用拼音!)
<template>
<div class="zi-ding-yi-zujian">
我是个很牛的input
<input type="text" v-model="value">
</div>
</template>
<script>
export default {
data(){
return{
value:'很牛的v-model'
}
}
}
</script>
再建一个页面zidingyi.vue调用组件
<template>
<div class="zi-ding-yi">
自定义组件测试
<zidingyizujian></zidingyizujian>
</div>
</template>
<script>
import zidingyizujian from './zidingyizujian.vue'
export default {
components:{
zidingyizujian
}
}
</script>
ok, 我的想法是新增一个comModel指令控制组件里面的input的v-model。
在组件zidingyizujian.vue
export default {
data(){
return{
value:'很牛的v-model'
}
},
directives: {
model: {
// 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind:function(){
},
//被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
inserted: function (el,binding,vnode,oldVnode) {
console.log('inserted,','el:',el,'binding:',binding,'vnode:',vnode,'oldVnode:',oldVnode)
},
//所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。
//但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
update:function(){
},
//指令所在组件的 VNode 及其子 VNode 全部更新后调用。
componentUpdated:function(){
},
//只调用一次,指令与元素解绑时调用。
unbind:function(){
}
}
}
}
1.el
就是那个组件的dom
2.binding
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”
3.vnode 虚拟dom
虚拟dom的字段比较多,我们着重看context这个属性,有想了解更多的朋友可以看官方文档的vue虚拟节点文章:https://cn.vuejs.org/v2/api/#VNode-%E6%8E%A5%E5%8F%A3
简单来讲,分为两步,1.set,2.get.(双向绑定的核心)
那么第一步set:
通过el节点获取它的子节点input。监听input输入,将input输入的值传给页面绑定pageValue。 这里就要用到虚拟节点vnode和指令属性binding来获取:vnode.context[binding.expression]
第二步get:
通过钩子函数update:function(){} 实时监听当前指令绑定的页面的值变化。然后更新到子组件。
思路有了,接下来就是代码:
export default {
components:{
zidingyizujian,
},
data(){
return{
pageValue:'这是页面的v-model'
}
},
created(){
},
directives: {
'comModel': {
// 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind:function(el,binding,vnode,oldVnode){
},
//被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
inserted: function (el,binding,vnode,oldVnode) {
console.log(el,'el')
console.log(binding,'binding')
console.log(vnode,'vnode')
console.log(oldVnode,'oldVnode')
let input = el.getElementsByTagName('input')[0];
input.oninput = e => {
let value = e.target.value;
vnode.context[binding.expression] = value;
}
},
//所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。
//但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
update:function(el,binding,vnode,oldVnode){
let input = el.getElementsByTagName('input')[0];
input.value = binding.value;
},
//指令所在组件的 VNode 及其子 VNode 全部更新后调用。
componentUpdated:function(){
},
//只调用一次,指令与元素解绑时调用。
unbind:function(){
}
}
}
}