Vue 2.0学习笔记:自定义指令
在Vue中为了更好的操作DOM元素,其内置了一些指令,比如v-model
、v-if
、v-show
、v-text
、v-html
、v-for
和v-bind
等。除此之外,Vue也允许注册自定义指令。这些自定义指令可以说我们对普通DOM元素进行底层操作。比如@SARAH DRASNER写的一篇有关于Vue自定义指令的文章,简单易懂。今天自己也仔细撸了一下Vue中怎么实现自定义的指令。
钩子函数
创建自定义指令,在Vue中一个指令定义对象可以提供下面几个钩子函数,而这几个钩子函数都是可选的:
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置inserted
:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)update
:所在组件的VNode
更新时调用,但是可能发生在其子VNode
更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新componentUpdated
:指令所在组件的VNode
及其子VNode
全部更新后调用unbind
:只调用一次,指令与元素解绑时调用
上图来自于《The Power of Custom Directives in Vue》一文。
来看一个简单的示例,看看这些钩子函数的触发时机。
至于怎么自定义一个指令,先不阐述。在Vue中通过Vue.directive('directiveName', {...})
方式来注册一个指令。在实际调用的时候需要前面添加v-
来使用。有关于这方面的细节,我们稍后再阐述。
Vue.directive('hello', {
// 只调用一次,指令第一次绑定到元素时调用
bind: function (el) {
console.log('触发bind钩子函数!')
},
// 被绑定元素插入父节点时调用
inserted: function (el) {
console.log('触发inserted钩子函数!')
},
// 所在组件的`VNode`更新时调用,但是可能发生在其子元素的`VNode`更新之前
update: function (el) {
console.log('触发update钩子函数!')
},
// 所在组件的`VNode`及其子元素的`VNode`全部更新时调用
componentUpdated: function (el) {
console.log('触发componentUpdated钩子函数!')
},
// 只调用一次,指令与元素解绑时调用
unbind: function (el) {
console.log('触发unbind钩子函数!')
}
})
let myComponent = {
template: '<h1 v-hello>{{ message }}</h1>',
props: {
message: String
}
}
let app = new Vue({
el: '#app',
data () {
return {
message: 'Hello! 大漠'
}
},
components: {
myComponent: myComponent
},
methods: {
update: function () {
this.message = 'Hi! 大漠'
},
uninstall: function () {
this.message = ''
},
install: function () {
this.message = 'Hello!W3cplus'
}
}
})
<div id="app">
<my-component v-if="message" :message="message"></my-component>
<div class="actions">
<button @click="update">更新</button>
<button @click="uninstall">卸载</button>
<button @click="install">安装</button>
</div>
</div>
当页面加载时就触发了bind
和inserted
两个钩子函数:
当我们点击“更新”按钮,将会更改message
的值,会触发组件myComponent
更新。
这个时候触发了update
和componentUpdated
两个钩子函数。
接下来我们再点击“卸载”按钮,message
的数据将会置空,这个时候传给v-if
的值为false
,将会触发组件myComponent
组件卸载。
此时触发了unbind
钩子函数。
最后我们再点击“安装”按钮,message
将会重新被赋值,触发myComponent
组件重新安装。
这个时候触发了bind
和inserted
两个钩子函数。
通过上面这个简单的示例,我们对五个钩子函数的触发时间有了一个初步的认识。对于我这样的初学者而言,对bind
和inserted
、update
和componentUpdated
之间的区别还是存在一定的疑问。为了解惑,在上面的示例的基础上,再来做简单的测试。
首先来看bind
和inserted
之间的区别,Vue的官网文档是这样对两个钩子函数进行描述的:
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置inserted
:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)
修改一下上例中的代码:
bind: function (el) {
console.log(el.parentNode)
console.log('触发bind钩子函数!')
},
inserted: function (el) {
console.log(el.parentNode)
console.log('触发inserted钩子函数!')
}
从上图中我们可以看出,在bind
钩子函数被触发时,其父节点为null
,而inserted
钩子函数触发时,父节点是存在的。
再来看update
和componentUpdated
两个钩子函数间的区别,同样先来看官方规范的描述:
update
:所在组件的VNode
更新时调用,但是可能发生在其子VNode
更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新componentUpdated
:指令所在组件的VNode
及其子VNode
全部更新后调用
从描述来看,这两个钩子函数都和组件更新周期有关。同样的,基于前面的示例,修改一下update
和componentUpdated
两个钩子函数中的代码:
update: function (el) {
console.log(el.parentNode)
console.log(el.innerHTML)
console.log('触发update钩子函数!')
},
componentUpdated: function (el) {
console.log(el.parentNode)
}
从控制台输出的结果可以看出update
和componentUpdated
两个钩子函数就是组件更新前和更新后的区别。
前面也提到过了,创建Vue的自定义指令的这五个钩子函数都是可选的,不一定要全部出现。而这其中bind
和update
两个钩子函数是最有用的。在实际使用的时候,我们应该根据需求做不同的选择。比如在恰当的时间通过bind
钩子函数去初始化实例,update
钩子函数去做对应的参数更新和使用unbind
钩子函数去释放实例资源占用等。
另外,这些钩子函数都带有参数,即el
、binding
、vnode
和oldVnode
。
bind(el, binding, vnode)
inserted(el, binding, vnode)
update(el, binding, vnode, oldVnode)
componentUpdated(el, binding, vnode, oldVnode)
unbind(el, binding, vnode)
接下来我们看看钩子函数的参数。
钩子函数参数
指令钩子函数会被传入以下参数:
el
:指令所绑定的元素,可以用来直接操作DOMbinding
:一个对象,这个对象包含一些属性,稍后列出每个属性的含义vnode
:Vue编译生成的虚拟节点。有关于VNode
更多的资料,可以阅读VNode
相关的APIoldVnode
:上一个虚拟节点,仅在update
和componentUpdated
两个钩子函数中可用
binding
参数是一个对象,其包含以下一些属性:
name
:指令名,不包括v-
前缀value
:指令的绑定值,如例v-hello = "1 + 1"
中,绑定值为2
oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用,无论值是否改变都可用expression
:字符串形式的指令表达式。例如v-hello = "1 + 1"
中,表达式为"1 + 1"
arg
:传给指令的参数,可选。例如v-hello:message
中,参数为"message"
modifiers
:一个包含修饰符的对象。例如v-hello.foo.bar
中,修饰符对象为{foo:true, bar:true}
除了
el
之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的dataset
来进行。
在很多时候,你可能想在 bind
和 update
时触发相同行为,而不关心其它的钩子。比如这样写:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
如果指令需要多个值,可以传入一个 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!"
})
创建一个自定义指令
接下来,咱们来通过实例来看看怎么创建Vue的自定义指令。
模态(Modal)组件是我们经常看得到的组件。该Modal组件包含了一些文本输入框,而这个输入框是根据v-for
指令动态生成。当切换到可见状态时,第一个输入框将会自动获得焦点。
实现自动获取第一个输入框得到焦点,此时需要一个自定义指令,这里把他称为**v-focus
**指令。
// 首先需要注册一个新指令,它会自动添加v-前缀
Vue.directive('focus', {
componentUpdated: function (el, binding, vnode) {
// 当binding.value 绑定的值为true,文本框获取焦点
if (binding.value) {
el.focus()
}
}
})
添加Modal组件的基本代码:
let app = new Vue({
el: '#app',
data () {
return {
fields: [
'姓名',
'邮箱',
'地址',
'电话'
],
modalIsOpen: false
}
},
methods: {
toggleModal: function () {
this.modalIsOpen = !this.modalIsOpen
}
},
computed: {
buttonText: function () {
return this.modalIsOpen ? '关闭' : '打开'
}
}
})
在HTML中写入所需要的模板:
<div id="app">
<button @click="toggleModal">{{ buttonText }}</button>
<transition name="modal-toggle">
<div class="modal" v-show="modalIsOpen">
<input type="text" v-for="(field, key) in fields" v-focus="key === 0" :placeholder="field" />
</div>
</transition>
</div>
最终效果如下:
打开Modal框时,第一个输入框将自动会获得焦点:
大家记得<textarea>
这样的表单控件元素,我们有一个属性maxlength
属性,用来控制表单控件最长输入的字符数。如果不设置这个属性,我们可以使用Vue自定义的指令来完成,比如我们创建一个v-maxchars
的指令。
Vue.directive('maxchars', {
bind: function (el, binding, vnode) {
let maxChars = binding.expression
let handler = function (e) {
if (e.target.value.length > maxChars) {
e.target.value = e.target.value.substr(0, maxChars)
}
}
el.addEventListener('input', handler)
}
})
比如我们来做一个Twitter发推的小组件:
let app = new Vue({
el: '#app',
data () {
return {
imgUrl: '//pbs.twimg.com/profile_images/468783022687256577/eKHcWEIk_normal.jpeg',
content: '',
totalcount: 140
}
},
computed: {
reduceCount () {
return this.totalcount - this.content.length
}
}
})
对应的模板:
<div id="app">
<div class="twitter">
<img :src="imgUrl" />
<div class="content">
<textarea v-model="content" v-maxchars="140">有什么新鲜事情?</textarea>
<p>您还可以输入{{ reduceCount }}字</p>
</div>
</div>
</div>
效果如下:
感兴趣的同学,可以体验一下。当你输入到第141
个字的时候,将无法继续输入。
上面我们看到的都是通过Vue.directive('your-directive-name', {...})
注删的全局自定义指令。除此之外,还可以在组件中使用directives
的选项,注册一个局部的自定义指令。
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
总结
在Vue中除框架自带的一些内置指令可以操作DOM之外,还可以通过Vue的Vue.directive()
和组件的directives
选项,分别注册一个全局指令和局部指令。每个指令都有五个钩子函数。这些函数可以帮助我们实现所需要的自定义指令功能。文章末尾通过两个简单的示例,演示了在Vue中怎么创建自定义的指令,以及如何使用这些自定义的指令。
由于自身是Vue的初学者,如果文章中有不对之处,还请各路大婶拍正,如果你在这方面有更好的建议或经验,欢迎在下面的评论中与我们一起分享。
如需转载,烦请注明出处:https://www.fedev.cn/vue/custom-directive.htmlTravis Scott x Jordan 1 Backwards Swoosh Mocha