有时候我们想要封装自己的指令(用于对DOM执行某些操作),vue 提供了自定义指令接口满足了我们这种需要。定义方式同样分为全局和局部。
1. 全局自定义指令
语法:Vue.directive('指令名字', 指令配置对象)
Vue.directive('color', {
// 绑定到元素上时触发的函数
bind(el, binding, vnode) {
el.style.color = "#f00";
}
})
使用方式如下:
<!-- 直接在元素上添加指令即可 -->
<p v-color>两个黄鹂鸣翠柳</p>
上方的代码定义了一个v-color
指令,当它被添加到元素上时,元素的文字颜色会变成红色。
当我们想要知道它怎么实现时,我们需要先了解指令配置对象包含什么?
1.1 指令配置对象上的钩子函数
钩子函数可以简单理解为是一种系统在不同的阶断自动执行、用户无须干预的函数。很多开发语言中都有这个称呼,这里你可以简单的理解为钩子函数就是监听过程触发的函数。我们前面讲到的window.onload
就是页面加载的钩子函数,AJAX中的onloadstart
、onprogress
、onloadend
等都是钩子函数。
bind
:只调用一次,指令第一次绑定到元素时调用(这个时候还没插入DOM)。在这里可以进行一次性的初始化设置。一般在函数内执行样式类名等操作。inserted
:当指令绑定的元素插入到父节点时执行的钩子函数update
:所在组件的Vnode更新时调用,但是可能发生在其子元素的Vnode更新之前componentUpdate
:指令所在组件的Vnode及其子Vnode全部更新时调用unbind
:只调用一次,指令与元素解绑时
1.2 钩子函数的参数
指令钩子函数会被传入以下参数:
el
:指代的是绑定自定义指令的那个元素binding
:当前自定义指令对象,包含跟指令相关的信息属性
name
:指令名,不包括 v- 前缀value
:指令的绑定值,例如:v-my-directive="2"
中,绑定值为 2expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为 “1 + 1”。arg
:传给指令的参数,例如:v-my-directive:foo
,arg
的值为 ‘foo’modifiers
:一个包含修饰符的对象,例如:v-my-directive.a.b
,modifiers
的值为{'a':true,'b':true}
oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。
vnode
:Vue编译的生成虚拟节点
用一个示例理解binding
中各个参数的含义:
<div id="app">
<!-- 可以和 vu e中的指令做类比 -->
<button v-on:click.stop="clickHandler"></button>
<!-- 自定义一个指令 -->
<div v-demo:foo.a.b="message">我是自定义的指令</div>
</div>
<script type="text/javascript">
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'指令名: ' + binding.name + '<br>' +
'绑定的值: ' + s(binding.value) + '<br>' +
'完整的指令: ' + s(binding.expression) + '<br>' +
'指令的参数: ' + binding.arg + '<br>' +
'指令的修饰符: ' + s(binding.modifiers)
}
})
var vm = new Vue({
methods: {
clickHandler(){}
}
}).$mount('#app')
</script>
2. 局部自定义指令
如果要注册一个局部指令,实例可以接收一个directives
选项,用于添加自定义指令:
<div id="app">
<input v-focus type="text" />
</div>
<script type="text/javascript">
vm = new Vue({
// 局部自定义指令
directives: {
// 指令名:{ 钩子函数 }
focus: {
// 插入到父节点的生命周期钩子
inserted: function (el) {
el.focus()
}
}
}
}).$mount('#app')
</script>
3. 自定义指令的简写
在很多时候,我们只关注在bind
和update
时触发相同行为,而不关心其它的钩子,这样我们可以可以把指令简写为一个函数,表示仅在bind
和update
时触发。
<div id="app">
<!-- 自定义指令,修改元素的背景颜色为绿色 -->
<span v-color="green">文字的背景颜色是绿色</span>
</div>
<script type="text/javascript">
/* 全局的简写,第二个参数直接传递一个函数 */
Vue.directive('color', function (el, binding) {
el.style.backgroundColor = binding.value
})
var vm = new Vue({
data(){
return {
green: '#0f0'
}
},
// 局部的简写
directives: {
// 直接接受一个函数作为属性值
color: function(el, binding) {
el.style.backgroundColor = binding.value
}
}
}).$mount('#app')
</script>
1. 对象问题
由于 vue 会在初始化实例时对对象的 property 执行了 getter/setter 转化,所以 property 必须在 data 中声明的对象上存在才能具有响应式。
示例:
<div id="app">
<p> obj ------- {{obj}}</p>
<button @click="changeObj">修改obj的内容</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data(){
return {
obj: {
name: '李白'
}
}
},
methods: {
changeObj(){
// 添加在data中声明的obj内没有的属性,不会引起视图更新
this.obj.age = '24'
}
}
})
</script>
对于已经创建的实例,vue 不允许动态添加根级别的响应式 property。但是,我们可以使用Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式 property。修改上例:
let vm = new Vue({
el: '#app',
...
methods: {
changeObj(){
// 添加在data中声明的obj内没有的属性,不会引起视图更新
// this.obj.age = '24'
// 通过该方法,可以让 age 属性具有响应式
Vue.set(this.obj, 'age', 24)
}
}
})
还可以使用vm.$set
实例方法,这也是全局Vue.set
方法的别名:
this.$set(this.obj, 'age', 24)
有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
2. 数组问题
vue 不能检测以下数组的变动,比如利用索引直接设置一个数组项时,或直接修改数组的长度时,举个例子:
<div id="app">
<p> arr ------- {{arr}}</p>
<button @click="changeArr">修改arr的内容</button>
</div>
<script>
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
},
changeArr(){
this.items[1] = 'x' // 不是响应性的
this.items.length = 2 // 不是响应性的
}
})
</script>
我们同样可以使用Vue.set
和this.$set
来处理这些问题,当然调用数组的方法也可以实现相同的效果:
Vue.set(this.items, 1, 'x')
this.$set(this.items, 1, 'x')
this.items.splice(1, 1, 'x')
this.items.splice(2) // 修改长度