目录
0x00 局部注册
局部注册的 自定义指令 只能在该vue文件中使用,全局注册的自定义指令在任何文件中都可以使用
钩子函数:
- bind: 只调用一次, 指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
- inserted: 被绑定元素插入到DOM树时调用
- update: 绑定元素的值 更新时调用,因为可能多个 元素都绑定了该自定义指令,所以只要有一个元素的值发生了更新,update就会被触发很多次。
componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
DEMO: 当input框插入DOM树时,自动获取焦点
<template>
<div>
<!-- 基础 -->
<input type="text" class='form-control' v-focus>
</div>
</template>
<script>
export default{
directives:{
//自定义指令名称
focus:{
//第一次绑定时
//el 绑定的dom节点,binding 绑定的信息
bind(el,binding,vNode){
},
//被绑定元素 插入到父节点时
inserted(el,binding){
el.focus()
},
//节点更新的时候
update(el,binding){
}
}
}
}
</script>
<style>
</style>
demo2:
<template>
<div>
<!-- 列表更新 -->
<ul class='list-group'>
<li class='list-group-item' v-for='(item,index) in list' :key='index' v-list='item' @click='deleteItem(index)'>{{item}}</li>
</ul>
</div>
</template>
<script>
export default{
data(){
return {
list:[1,2,3,4,5,6,7,8,9]
}
},
directives:{
//自定义指令名称
list:{
//第一次绑定时
//el 绑定的dom节点,binding 绑定的信息
bind(el,binding,vNode){
console.log('----bind-----')
},
//被绑定元素 插入到父节点时
inserted(el,binding){
console.log('----inserted-----')
},
//节点更新的时候,如果有多个节点都绑定了该自定义指令,只要有一个节点更新了,每个节点绑定的指令的update都会被触发一次,所以需要判断oldValue 和 newValue是否相等来 执行相应的代码
update(el,binding){
if(binding.oldValue !== binding.value){
console.log('----update----')
}
}
}
},
methods:{
setItem(index){
// this.list[index] = Math.floor(Math.random()*10) //这种方式不能实现更新
let random_num = Math.floor(Math.random()*10)
this.$set(this.list,index,random_num)
},
deleteItem(index){
//如果删除了第一个,那么其余li都会被重新渲染一遍,update会被触发8次,且oldValue 和 newValue不相等
this.list.splice(index,1)
}
}
}
</script>
<style>
</style>
0x01 全局注册
案例:通过自定义指令实现骨架屏
思路:当指令绑定到元素上时,给元素初始化背景颜色 和字体颜色
这时后端的数据还没有接收到,当接收到后端传来的数据时,
元素的值就会发生变化,进而会触发 update方法,我们在update方法中 将给元素初始化的背景颜色 和字体颜色去除掉。
<template>
<div>
<div class='row'>
<div class='col-4 mb-3' v-for="(item,index) in imgs" :key='index'>
<img :src="item.src" class='w-100 mb-2 rounded' v-skeleton.img='item.src' style='min-height:50px;'>
<h6 class='w-100' style='min-height:30px' v-skeleton.text='item.name'>{{item.name}}</h6>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
imgs:[
]
}
},
created(){
for(let i=0;i<6;i++){
this.imgs.push({src:"",name:""});
}
},
//页面渲染完成后
mounted() {
//模拟请求数据
setTimeout(()=>{
this.imgs=[
{src:"http://qiniu.databankes.cn/databank.png",name:"DataBank西电站"},
{src:"http://qiniu.databankes.cn/databank.png",name:"DataBank西电站"},
{src:"http://qiniu.databankes.cn/databank.png",name:"DataBank西电站"},
{src:"http://qiniu.databankes.cn/databank.png",name:"DataBank西电站"},
{src:"http://qiniu.databankes.cn/databank.png",name:"DataBank西电站"},
{src:"http://qiniu.databankes.cn/databank.png",name:"DataBank西电站"},
]
},1000)
},
methods: {
setItem(index) {
// this.list[index] = Math.floor(Math.random()*10) //这种方式不能实现更新
let random_num = Math.floor(Math.random() * 10)
this.$set(this.list, index, random_num)
},
deleteItem(index) {
//如果删除了第一个,那么其余li都会被重新渲染一遍,update会被触发8次,且oldValue 和 newValue不相等
this.list.splice(index, 1)
}
}
}
</script>
<style>
</style>
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
//骨架屏指令
Vue.directive('skeleton', {
//第一次绑定时
//el 绑定的dom节点,binding 绑定的信息
bind(el, binding, vNode) {
console.log('----bind-----')
el.style.backgroundColor = '#eee'
el.style.color = '#eee'
},
update(el, binding) {
if (binding.oldValue !== binding.value) {
console.log('----update----')
//去除骨架屏
el.style.backgroundColor = ''
el.style.color = ''
//渲染真正的值
for (let key in binding.modifiers) {
switch (key) {
case 'img':
el.src = binding.value
break;
case 'text':
el.innerHTML = binding.value
}
}
}
}
})
new Vue({
render: h => h(App),
}).$mount('#app')
0x02 案例:自定义右键菜单
<template>
<div>
<ul class='list-group'>
<li class='list-group-item' v-for="(item,index) in list" v-menu='item.menu' :key='index'>{{item.name}}</li>
</ul>
<!-- 下拉菜单样式 -->
</div>
</template>
<script>
export default {
data() {
return {
list: [{
name: "列表1",
menu: [{
name: "列表1的选项一"
},
{
name: "列表1的选项二"
}
]
},
{
name: "列表1",
menu: [{
name: "列表2的选项一"
},
{
name: "列表2的选项二"
}
]
}
]
}
},
directives: {
menu: {
//插入到dom树
inserted(el, binding) {
//监听鼠标右键单击事件
el.addEventListener('contextmenu', (e) => {
//取消默认行为
e.preventDefault()
let getSubmenu = function() {
return document.getElementById('sub-menu')
}
//关闭之前的菜单
let removeSubmenu = function() {
let submenu = getSubmenu()
if (submenu) {
submenu.remove()
}
}
removeSubmenu()
//拿到鼠标坐标
let left = e.x;
let top = e.y;
let list = '';
//构建菜单列表
for (let i = 0; i < binding.value.length; i++) {
list += "<li class='list-group-item list-group-item-primary list-group-item-action'>" + binding.value[i].name +
"</li>"
}
//构建菜单
let template =
`
<div id='sub-menu'>
<!--让蒙版占满屏幕-->
<div style="position:fixed;top:0;left:0;bottom:0;right:0"></div>
<ul class='list-group' style='width:200px;position:fixed;top:${top}px;left:${left}px;z-index: 999;'>
${list}
</ul>
</div>
`;
//渲染到页面
el.innerHTML += template;
//给蒙版增加点击事件
let currentSubmenu = getSubmenu()
//获取到蒙版
currentSubmenu.children[0].addEventListener('click', (e) => {
//关闭菜单
removeSubmenu()
})
//给菜单增加点击事件
let lis = currentSubmenu.getElementsByClassName('list-group-item')
for (let i = 0; i < lis.length; i++) {
lis[i].addEventListener('click', () => {
console.log(binding.value[i].name)
removeSubmenu()
})
}
})
}
}
}
}
</script>
<style>
</style>