1、场景
对底层的DOM进行操作的时候,会用到自定义指令
2、注册指令分为全局和局部
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
}
})
// 注册一个局部自定义指令 `v-focus`
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
}
}
}
<input v-focus />
4、自定义指令的钩子函数
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
unbind:只调用一次,指令与元素解绑时调用
5、钩子函数的参数
el:指令所绑定的元素,可以用来直接操作 DOM
binding:一个对象,包含以下 property:
`name`:指令名,不包括 v- 前缀。
`value`:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
`oldValue`:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。
无论值是否改变都可用。
`expression`:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
`arg`:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
`modifiers`:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,
修饰符对象为 { foo: true, bar: true }
`vnode`:Vue 编译生成的虚拟节点
`oldVnode`:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
6、常用案例
1. 防抖
2. input聚焦
3. 图片懒加载
4. 一键copy
5. 拖曳
6. 下拉菜单
7. 相对时间转换
7、input聚焦
//全局注册
<body>
<div id="app">
<input type="text" v-focus>
</div>
<script>
Vue.directive('focus', {
// inserted 是钩子函数 每一个钩子函数都有2个参数 el,binding
inserted: function (el) {
el.focus();
}
})
var app = new Vue({
el: '#app',
})
</script>
</body>
//局部注册
<body>
<div id="app">
<input type="text" v-focus>
</div>
<script>
var app = new Vue({
el: '#app',
// 自定义指令
directives: {
// 自定义指令的名称-v-focus
focus: {
// 钩子函数
inserted: function (el) {
el.focus();
}
}
}
})
</script>
</body>
8、 防抖
自定义指令按照传参的方式执行
<body>
<div id="app">
<button v-debounce="{ev:'click',fn:sayHello,delay:1000}">提交</button>
</div>
<script>
var app = new Vue({
el: '#app',
directives: {
debounce: {
bind: function (el, binding) {
// 参数解构
let { ev, fn, delay } = binding.value;
let timer;
el.addEventListener(ev, () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn();
}, delay)
})
}
}
},
methods: {
sayHello() {
console.log('hello!')
}
},
})
</script>
</body>
频繁点击,只有最后一次触发
9、时间转换
Vue.directive('time', {
bind: function (el, binding) {
el.innerHTML = Time.getFormatTime(binding.value);
// 定时器 el.__timeout__ 每分钟触发一次,更新时间
el.__timeout__ = setInterval(function () {
// 将格式化时间写入指令所在元素
el.innerHTML = Time.getFormatTime(binding.value);
}, 60000); // 注意这里的60000是毫秒
},
unbind: function (el) {
// 在 unbind 钩子里清除定时器。
clearInterval(el.__timeout__);
delete el.__timeout__;
}
})
10、自己实现v-if指令
实现原理是通过注释节点,注释节点不会在DOM中渲染
kif: {
inserted: function (el, binding, vnode) {
// 如果v-if的值是true,代表要隐藏元素
if (binding.value) {
// 创建一个注释节点
let comment = document.createComment(' ')
Object.defineProperty(comment, 'setAttribute', {
//设置值是空
value: () => undefined
})
// ================操作当前节点的各属性
vnode.elm = comment
vnode.text = ' '
vnode.isComment = true
vnode.context = undefined
vnode.tag = undefined
vnode.data.directives = undefined
// 组件实例存在
if (vnode.componentInstance) {
vode.componentInstance.$el = comment;
}
// ==================父节点替换
if (el.parentNode) {
el.parentNode.replaceChild(comment, el)
}
}
}
}
}
通过给标签设置v-kif="false" 和 v-kif="true",对比一下,vnode的区别
通过F12查看elements可以看到我们实现的注释节点