Vue3.0中一些好用的指令封裝總結(jié)
一. 前言
最近在維護(hù)一個老項目,發(fā)現(xiàn)項目中用了很多的自定義指令。突然發(fā)現(xiàn),自己已經(jīng)很久沒有封裝過指令了,特別是vue3.0時代以來,這個功能我好像用的越來越少了。其實以前在2.0時代的時候,封裝指令是很常見的寫法(勾起了當(dāng)年的回憶)。那現(xiàn)在既然遇到了,就封裝一些很常見的指令在日常開發(fā)中可以用到的。

二. 使用流程
2.1. 介紹
其實關(guān)于指令的使用,vue3.0的官網(wǎng)寫的已經(jīng)很仔細(xì)了,對新手很友好這里是地址。寫法主要還是在提供在不同鉤子函數(shù)中,執(zhí)行不同操作。
下面 這些是官網(wǎng)給出的鉤子函數(shù)的執(zhí)行時機
const myDirective = {
// 在綁定元素的 attribute 前
// 或事件監(jiān)聽器應(yīng)用前調(diào)用
created(el, binding, vnode) {
// 這個created可以拿到dom、但是binding不會檢測到響應(yīng)式數(shù)據(jù)的改變
},
// 在元素被插入到 DOM 前調(diào)用
beforeMount(el, binding, vnode) {},
// 在綁定元素的父組件
// 及他自己的所有子節(jié)點都掛載完成后調(diào)用
mounted(el, binding, vnode) {},
// 綁定元素的父組件更新前調(diào)用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在綁定元素的父組件
// 及他自己的所有子節(jié)點都更新后調(diào)用
updated(el, binding, vnode, prevVnode) {},
// 綁定元素的父組件卸載前調(diào)用
beforeUnmount(el, binding, vnode) {},
// 綁定元素的父組件卸載后調(diào)用
unmounted(el, binding, vnode) {}
}這里要注意幾個參數(shù):
el: 綁定到指令的dom元素。binding: 一個對象,包含以下屬性。value: 傳遞給指令的值。(v-directive="[1,2,3]",這里的[1,2,3]就是值)oldValue:之前的值。僅在beforeUpdate和updated中可用arg: 傳遞給指令的參數(shù)。(v-directive:arg="", 這個arg就是參數(shù))modifiers: 指令修飾符。(v-directive.foo="" 這里的foo就是修飾符)
這里就是要區(qū)分一下arg和modifiers的區(qū)別。舉個例子:
v-directive:arg.foo="1"
這里的話,value就是1、 foo就是指令修飾符, arg就是指令參數(shù)。
這里要注意點,指令的參數(shù)也可以是動態(tài)的:
v-directive:[arg].foo="1"
這時候指令的參數(shù)會基于組件的 arg 數(shù)據(jù)屬性響應(yīng)式地更新。
2.2. 簡單實用
這里簡單實現(xiàn)一個v-if,順便把directive的結(jié)構(gòu)搭建了。
新建一個directives文件夾,里面新建一個myShow.ts:
import type { Directive, DirectiveBinding } from 'vue'
const myShow: Directive = {
updated(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding
if (value) {
el.style.display = 'block'
}
else el.style.display = 'none'
}
}
export default myShow根據(jù)外部傳來的binding.value,來控制當(dāng)前元素的顯示與隱藏。要注意一下,這里放在update中執(zhí)行,因為在mounted中,不會檢測到binding.value值的改變。
然后在同級目錄下,新建一個index.ts
import type { App, Directive } from "vue"
import myShow from "./myShow"
interface IdirectiveList<T> {
[key: string]: T
}
const directiveList: IdirectiveList<Directive> = {
myShow
}
const installDirectives = {
install(app: App<Element>) {
for(let key in directiveList) {
app.directive(key, directiveList[key]) // 通過app.directive注冊
}
}
}
export default installDirectives最后在main.ts中把directives導(dǎo)入:
import installDirectives from './directives' app.use(installDirectives)
三. 常見指令實現(xiàn)
實現(xiàn)之前要提一句話,組件是主要的構(gòu)建模塊,而組合式函數(shù)則側(cè)重于有狀態(tài)的邏輯。自定義指令主要是為了重用涉及普通元素的底層 DOM 訪問的邏輯。 真不愧是官網(wǎng)的原話,講的很有深意!我們使用指令的目的是為了復(fù)用dom元素的一些操作方式。所以我們的注意點是 dom元素?。。?/p>
3.1 圖片懶加載
圖片懶加載的實現(xiàn)流程:
- 判斷元素是否進(jìn)入可視區(qū)域 使用
IntersectionObserver觀察 - 進(jìn)入可視區(qū)后加載圖片預(yù)定url
- 最后移除圖片觀察器
我們簡單實現(xiàn)一下:新建一個lazyImage.ts:
import loadingImage from '../assets/loading.gif'
import errorImage from '../assets/error.jpeg'
import type { Directive, DirectiveBinding } from "vue";
const lazyImage: Directive = {
mounted(el, binding: DirectiveBinding) {
const {src, loading = loadingImage, error = errorImage } = binding.value
el.src = loading // 先個一個默認(rèn)的占位圖片
const observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach((entry: IntersectionObserverEntry) => {
if (entry.isIntersecting) {
imageLoading(src).then(() => {
el.src = src
observer.unobserve(el) // 去除觀察
}).catch(() => {
el.src = error
})
}
})
}, {
rootMargin: '50px' //圖片 距離50px的時候開始加載
})
observer.observe(el)
el.dataset.observer = observer
},
unmounted(el) {
if (el.dataset.observer) {
el.dataset.observer.unobserve(el)
}
}
}
const imageLoading = (src: string) => {
return new Promise((resolve, reject) => {
const img = new Image()
img.src = src
img.onload = resolve
img.onerror = reject
})
}
export default lazyImage代碼簡單講解一下:
el.src = loading剛進(jìn)入的時候給個默認(rèn)值loading。- 用
IntersectionObserver來監(jiān)控每個img圖片。當(dāng)圖片進(jìn)入可視區(qū)域(entry.isIntersecting)的時候,加載data里面的src。 observer.observe(el)對當(dāng)前元素開啟觀察。el.dataset.observer = observer的意思是,把當(dāng)前的觀察者observer,放到dataset屬性中。這步操作的意思是,當(dāng)圖片加載正確的url后,移除對當(dāng)前圖片的觀察。- 在unmounted中卸載,放在一直觀察泄漏內(nèi)存。
然后我們更新一下index.ts文件。最后在頁面上使用一下:
<div class="text">
<img
width="600" height="400"
v-for="(image, index) in arr"
v-lazyImage="{src: image}"
:key="index"
/>
</div>
<script setup lang="ts">
import test from './assets/test.jpeg'
const arr = [test, test, test, test, test, test, test, test]
</script>看一下最終的效果:

3.2 按鈕權(quán)限
這個在一些后臺管理的項目里面用到的很多。其實原理很簡單,就是根據(jù)接口返回、自定義的白名單中,來判斷你當(dāng)前按鈕的權(quán)限標(biāo)識在不在里面,如果在顯示,否則隱藏。
新建一個permission.ts文件:
import { type Directive, type DirectiveBinding } from "vue";
// 這里可以自定義或者從接口拿
const whiteBlock = ["per:page:1", "per:page:2", "per:page:3", "per:page:4"]
const permission: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
if (!binding.value) return;
else {
console.log(binding.value)
// 如果在白名單內(nèi),不做處理,不在直接隱藏。
!whiteBlock.includes(binding.value) ? el.style.display = 'none' : el.style.display = 'auto'
}
}
}
export default permission沒什么好說的,很簡單。就是通過傳入的值來判斷在不在白名單之內(nèi)就可以。在頁面上使用一下:
<el-button v-permission="'per:page:1'">per:page:1</el-button>
<el-button v-permission="'per:page:2'">per:page:2</el-button>
<el-button v-permission="'per:page:10'">per:page:10</el-button>
<el-button v-permission="'per:page:3'">per:page:3</el-button>let whiteBlock: string[] = [];
setTimeout(() => {
whiteBlock = ["per:page:1", "per:page:2", "per:page:3", "per:page:4"]
}, 3000)
沒什么問題,per:page:10不在白名單內(nèi),就不會顯示。
但是這里有個優(yōu)化點,需要提一下。如果你的白名單數(shù)據(jù)是從接口拿的,那不能在mounted階段處理邏輯問題。因為異步的問題,會出現(xiàn)el加載完成,但是whiteBlock還沒有獲取到。
比如我們把whiteBlock的賦值變成異步的。
let whiteBlock: string[] = [];
setTimeout(() => {
whiteBlock = ["per:page:1", "per:page:2", "per:page:3", "per:page:4"]
}, 500)這時候頁面就會變成空白。

所以,這里需要對邏輯改造一下,把whiteBlock變?yōu)轫憫?yīng)式數(shù)據(jù),然后通過watchEffect監(jiān)聽一下whiteBlock。代碼如下:
import { type Directive, type DirectiveBinding } from "vue";
import { ref, watchEffect } from "vue"
// 這里可以自定義或者從接口拿
let whiteBlock = ref<string[]>([]);
setTimeout(() => {
whiteBlock.value = ["per:page:1", "per:page:2", "per:page:3", "per:page:4"]
}, 500)
const permission: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
watchEffect(() => {
if (whiteBlock.value.length === 0 || !binding.value) return
!whiteBlock.value.includes(binding.value) ? el.style.display = 'none' : el.style.display = 'auto'
})
}
}
export default permissionok! 最后要注意一下,whiteBlock的獲取一般都放在登陸的時候和用戶信息一起返回,不然的話會出現(xiàn),按鈕閃爍消失的問題。
3.3 元素的拖拽
這個其實已經(jīng)有很好用的庫了,但是不妨礙我們在實現(xiàn)一下。新建一個drag.ts文件:
import type { Directive } from "vue";
const drag: Directive = {
mounted(el: HTMLElement) {
el.style.cursor = 'move'
el.onmousedown = (e: MouseEvent) => {
const disX: number = e.clientX - el.offsetLeft;
const disY: number = e.clientY - el.offsetTop;
document.onmousemove = (e: MouseEvent) => {
el.style.left = `${e.clientX - disX}px`;
el.style.top = `${e.clientY - disY}px`;
};
document.onmouseup = () => {
document.onmousemove = null;
};
}
},
}
export default dra然后在項目中使用一下:
<el-button v-drag style="position: absolute;">拖拽</el-button>
這里注意一下,我這種寫法是需要當(dāng)前文件定位為absolute的。
3.4 無限滾動
這個的實現(xiàn)原理,主要是判斷scrollHeight和scrollTop、clientHeight的關(guān)系。從網(wǎng)上找了一張圖:

如果滾動軸到達(dá) 視口底部 則執(zhí)行callback函數(shù)。所以要判斷一下: scrollHeight - scrollTop <= clientHeight
新建一個infiniteScroll.ts文件:
import type { Directive, DirectiveBinding } from "vue";
const infiniteScroll: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { arg = 0, value } = binding
const bottomDistance: number = el.clientHeight - Number(arg)
if (el.scrollHeight - el.scrollTop <= bottomDistance) {
value() // 執(zhí)行回調(diào)
}
},
}
export default infiniteScroll在項目中使用一下:
<div v-infiniteScroll:[distance]="callBack"> </div>
四. 總結(jié)
好用的一些指令還有很多,比如防抖、節(jié)流等。這里就不多說了。這里主要是提供一些好用的指令實現(xiàn)的方法。
到此這篇關(guān)于Vue3.0中一些好用的指令封裝的文章就介紹到這了,更多相關(guān)Vue3.0指令封裝內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue+Element+Springboot圖片上傳的實現(xiàn)示例
最近在學(xué)習(xí)前段后分離,本文介紹了Vue+Element+Springboot圖片上傳的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2021-11-11
解決VUE-npm ERR! C:\rj\node-v14.4.0-win-x64\nod問題
這篇文章主要介紹了解決VUE-npm ERR! C:\rj\node-v14.4.0-win-x64\nod問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04
vue實現(xiàn)在線進(jìn)制轉(zhuǎn)換功能
這篇文章主要介紹了vue實現(xiàn)在線進(jìn)制轉(zhuǎn)換功能,本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2025-04-04
Vue $router.push打開新窗口的實現(xiàn)方法
在Vue中,$router.push方法默認(rèn)不支持在新窗口中打開頁面,但通過結(jié)合window.open方法和$router.resolve方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-09-09

