自定义指令
生命周期钩子
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>
|