Vue用自定义指令实现v-on及v-model
自定义指令
tips: 如果你已了解自定义指令的基本使用,想知道它到底能做什么,请直接看案例。如果你还不清楚自定义指令是什么,可以翻到博客后面看一些简介
直接上案例
实现v-on
我们来模仿v-on实现一个简易的v-on,我们叫他v-coder,运行前请先引入vue2.x
<div id="app">
<div v-coder:click.stop="myClick">{{msg}}</div>
</div>
<script>
// 先是在全局定义一个指令 v-coder
Vue.directive('coder', (el, binding) => {
// 如果传给指令的参数时click,那我们就当做点击事件来对待
if (binding.arg === 'click') {
// 我们给el绑定一个原生的click事件
el.onclick = function (e) {
// 如果用户使用了.stop修饰符,说明用户不希望产生冒泡
if (binding.modifiers.stop) {
// 我们调用原生的阻止冒泡的方法
e.stopPropagation()
}
// 我们通过expression知道用户想要执行哪一个方法,于是我们调用这个方法
// 我们需要通过vnode.context来获取使用此指令的上下文,也就是Vue实例对象
vnode.context[binding.expression]()
}
}
})
// 一个勉强能用的自定义指令就这么创建完成了
const app = new Vue({
el: '#app',
data: {
msg: 'hello Vue',
},
methods: {
myClick() {
console.log('foo-foo')
},
},
})
</script>
实现v-model
我们来实现一个v-model,我们还是叫他v-coder,为什么还叫v-coder…没有为什么!
// 实际上我们可以只用到bind&update阶段,所以我们可以简化以下代码
<div id="app">
<input type="text" v-coder="iptVal" />
<button @click="changeIptVal">iptVal减一</button>
<div>iptVal:{{iptVal}}</div>
</div>
<script>
Vue.directive('coder', (el, binding, vnode) => {
// 给binding添加了一个eve记录当前需要用的事件
binding.eve = 'input'
// 如果使用了.lazy切换为Input事件
if (binding.modifiers.lazy) {
binding.eve = 'change'
}
// 实现元素修改后对数据的修改 vnode.context记录当前的上下文,也就是实例对象
el.addEventListener(binding.eve, function () {
vnode.context[binding.expression] = el.value
})
// 实现数据改变后视图的改变
el.value = binding.value
})
const app = new Vue({
el: '#app',
data: {
iptVal: '',
},
methods: {
// 方便你来验证的函数
changeIptVal() {
this.iptVal--
},
},
})
</script>
全局自定义指令
// 注册一个全局自定义指令 `v-color`
Vue.directive('color', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.style.color = "#00f"
}
})
局部自定义指令
在组件内自定义指令
components: {
cpn: {
template: '#cpn',
directives: {
color: {
bind(el, binding, vnode) {
console.dir(binding)
},
inserted(el, binding, vnode) {
console.log('inserted')
el.style.color = "#00f"
},
update(el, binding, vnode) {
console.log('update')
},
},
},
},
},
简写形式
如果不需要使用钩子函数,可以简化为以下,则会在bind&update阶段帮你准备好指令
我们一般都是在bind和update里来做一些必要操作,所以简写形式很常用
Vue.directive('color', function (el, binding) {
el.style.color = "#00f"
})
钩子函数
bind
只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted
被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中),也就是当父节点被渲染成功后就会触发。
update
所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。可以理解为当父节点发生变化时会触发的钩子。
componentUpdated
指令所在组件的 VNode 及其子 VNode 全部更新后调用。可以理解为当父节点及其本身都发生变化时会触发的钩子。
unbind
只调用一次,指令与元素解绑时调用。
钩子函数参数
el
指令所绑定的元素,可以用来直接操作DOM。
binding
对象中有以下属性,记录着我们需要的一些数据。
name 指令名不包括v-前缀 例:v-color, name保存的就是color
value 指令绑定的值 例:v-color=“1+1” ,value保存的就是2
oldValue 指令绑定的前一个值 ,也就是当指令绑定的值被改变后,oldValue会保留上一个存在的值
expression 指令绑定的表达式 例:v-color=“1+1” ,value保存的就是1+1
arg 传给指令的参数,例 v-color:foo=“foolish”,arg保存的就是foo
modifiers: 包含修饰符的对象,例 v-color:foo.big.circle=“foolish” modifiers保留的就是{big: true,circle: true}
vnode
Vue编译生成的虚拟节点。记录着与DOM元素相关的属性,我们后面的案例会用到vnode.context,这个属性记录着当前使用指令的上下文,也就是Vue的实例对象
oldVnode
上一个虚拟节点,仅在update和componentUpdated钩子中可用。
这里我们只讨论binding这个对象,我们只讲究实用性,关于vnode和oldVnode,有机会我们再探讨。
感谢阅读
文中可能并不是最恰当的使用方式,但是笔者发现很难从现有的帖子或文档中了解到自定义指令的使用方式,如果您有更好的意见或者简易,还望评论或私信交流!