自定义指令

生命周期钩子

Vue3自定义指令有这些生命周期钩子函数:

  • created:元素初始化时
  • beforeMount:指令绑定到元素后调用,只调用一次
  • mounted:元素插入父级dom调用
  • beforeUpdate:元素被更新前调用
  • update:元素被更新后调用
  • beforeUnmount:在元素被移除前调用
  • unmounted:指令被移除后调用,只调用一次

Vue2的指令:bind、inserted、update、componentUpdated、unbind

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<template>
<div>
<button @click="flag=!flag">切换</button>
<button @click="background=(background==='cyan'?'orange':'cyan')">更新</button>
<A v-if="flag" v-move:abc.def="{background}"></A>
</div>
</template>

<script setup lang="ts">
import A from "@/components/A.vue"
import type { DirectiveBinding } from "vue"
import type { Directive } from "vue"

const flag = ref<boolean>(true)
const background = ref<string>("cyan")

type Dir = {
background:string
}

// 命名规范:必须以v开头
const vMove: Directive = {
// 每一个钩子函数里都能收到传值、参数和修饰符
// 一般用到mounted、updated和unmounted比较多
/**
* 可以使用这些参数做些事情
* @param el 被绑定自定义指令的元素
* @param dir 传入的内容,包括参数、修饰符、传值、上次的传值、当前组件实例
* @param vNode 当前组件的虚拟DOM
* @param preVNode 上次的虚拟DOM
*/
created(el:HTMLElement, dir:DirectiveBinding<Dir>, vNode, preVNode) {
console.log("vMove created")
},
beforeMount(el, dir, vNode, preVNode) {
console.log("vMove beforeMount")
},
mounted(el, dir, vNode, preVNode) {
console.log("vMove mounted")
el.style.background = dir.value.background
},
beforeUpdate(el, dir, vNode, preVNode) {
console.log("vMove beforeUpdate")
},
updated(el, dir, vNode, preVNode) {
console.log("vMove Updated")
el.style.background = dir.value.background
},
beforeUnmount(el, dir, vNode, preVNode) {
console.log("vMove beforeUnmount")
},
unmounted(el, dir, vNode, preVNode) {
console.log("vMove Unmounted")
}
}
</script>

A.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="a">
<h1>A组件</h1>
</div>
</template>

<style scoped lang="scss">
.a {
width: 200px;
height: 200px;
border: 1px solid #ccc;
}
</style>

函数简写

如果只关心mounted和updated,并在两个时刻触发相同行为,而不关系其他的钩子函数,就可以使用简写。

权限校验案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<div class="btns">
<button v-has-show="'shop:create'">创建</button>
<button v-has-show="'shop:edit'">编辑</button>
<button v-has-show="'shop:delete'">删除</button>
</div>
</template>

<script setup lang="ts">
import type { Directive } from 'vue'

localStorage.setItem("userId","114514")

// mock后台返回的权限数据
const permission = [
"114514:shop:edit",
"114514:shop:create",
"114514:shop:delete",
]

const userId = localStorage.getItem("userId") as string
const vHasShow:Directive<HTMLElement,string> = (el:HTMLElement, binding:DirectiveBinding)=> {
// 如果权限表不包含这一条权限,隐藏,否则显示
if(!permission.includes(userId+":"+binding.value)) {
el.style.display = "none"
}
}
</script>

<style lang="scss">
.btns {
button {
margin: 10px;
}
}
</style>

内容拖拽案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<template>
<div v-move class="box">
<div class="header"></div>
<div>内容</div>
</div>
</template>

<script setup lang="ts">
import type { DirectiveBinding } from 'vue';
import type { Directive } from 'vue'

const vMove:Directive<any,void> = (el:HTMLElement, binding:DirectiveBinding)=> {
let moveElement:HTMLDivElement = el.firstElementChild as HTMLDivElement
const mouseDown = (e:MouseEvent)=> {
let X = e.clientX - el.offsetLeft
let Y = e.clientY - el.offsetTop
const move = (e:MouseEvent)=> {
el.style.left = e.clientX - X +"px"
el.style.top = e.clientY - Y +"px"
}
document.addEventListener("mousemove",move)
document.addEventListener("mouseup",()=>{
document.removeEventListener("mousemove",move)
})
}
moveElement.addEventListener("mousedown",mouseDown)
}
</script>

<style lang="scss">
.box {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
border: 1px solid #ccc;
.header {
height: 20px;
background: black;
cursor: move;
}
}
</style>

图片懒加载案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
<div>
<div>
<img v-lazy="item" width="350" height="350" v-for="(item,index) in arr" :key="index" alt="">
</div>
</div>
</template>

<script setup lang="ts">
import type { Directive } from 'vue'


// glob默认懒加载,就像这样
// let modules = {
// "xxx": ()=> import("xxxxx")
// }

// 开启eager是静态加载,就像这样
// import xxx from "xxxxx"

const imageList:Record<string,{default:string}> = import.meta.glob("./assets/images/*.*",{eager:true})
const arr = Object.values(imageList).map((v:any)=>v.default)

let vLazy:Directive<HTMLImageElement,string> = async (el,binding)=> {
// 默认展示加载图片
const def = await import("@/assets/loading.gif")
el.src = def.default

// 滑到可视区内再替换
// IntersectionObserver监控元素是否在可视区内
// 虚拟列表也可以通过这个API实现
// https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
const observer = new IntersectionObserver((en)=> {
// intersectionRatio 元素被展现的比例(不在视口外的比例)
if(en[0].intersectionRatio>0) {
el.src=binding.value
// 停止监听
observer.unobserve(el)
}
})
observer.observe(el)
}
</script>