【Vue.js】1711- 深入淺出 Vue3 自定義指令

Vue.js[1] 提供了豐富的指令來簡(jiǎn)化開發(fā)者的工作。除了內(nèi)置指令外,Vue.js 還支持自定義指令,開發(fā)者可以根據(jù)自己的需求擴(kuò)展 Vue.js 的指令庫(kù)。Vue.js 3.x 相較于 Vue.js 2.x 在自定義指令方面進(jìn)行了一些改進(jìn),本文將介紹 Vue.js 3.x 中自定義指令的使用方法。
? 什么是自定義指令
1. 概念介紹
在 Vue.js 中,指令 (Directives) 是一種帶有 v- 前綴的特殊屬性。它的作用是「當(dāng)其綁定的元素被插入到 DOM 中時(shí),會(huì)立即執(zhí)行一些行為」。
Vue.js 中有許多內(nèi)置指令,比如:
-
v-model:在表單元素上創(chuàng)建「雙向數(shù)據(jù)綁定」; -
v-show:根據(jù)表達(dá)式之真假值,「切換元素的 display CSS 屬性」; -
v-if:根據(jù)表達(dá)式之真假值「渲染或銷毀元素」; -
v-for:基于一個(gè)數(shù)組來渲染一個(gè)列表。
這些指令讓我們可以更加聲明式地操作 DOM,隱藏復(fù)雜的 DOM 操控邏輯。 除了內(nèi)置的指令,Vue.js 也允許我們注冊(cè)自定義指令[2]。自定義指令「允許我們?cè)阡秩镜?DOM 元素上應(yīng)用自定義的行為」。
2. 基礎(chǔ)使用
以全局自定義指令為例,通過全局方法 app.directive(name, options) 進(jìn)行注冊(cè),并使用 v- 前綴在模板中應(yīng)用。directive() 方法接收兩個(gè)參數(shù):
-
name:指令名稱,如focus; -
options:指令配置對(duì)象,其中包含「指令的鉤子函數(shù)」。
下面以自定義指令 v-focus作為示例介紹,首先創(chuàng)建 v-focus指令:
const?app?=?createApp({});
app.directive("focus",?{
??//?當(dāng)綁定元素插入到?DOM?中時(shí)......
??mounted(el)?{
????//?聚焦元素
????el.focus();
??},
});
然后在模板中使用:
<input?v-focus?/>
當(dāng)輸入框掛載到 DOM 時(shí),它將自動(dòng)獲得焦點(diǎn)。 一個(gè)自定義指令定義對(duì)象可以提供以下「鉤子函數(shù)」:
const?myDirective?=?{
??//?在綁定元素的?attribute?前
??//?或事件監(jiān)聽器應(yīng)用前調(diào)用
??created(el,?binding,?vnode,?prevVnode)?{
????//?下面會(huì)介紹各個(gè)參數(shù)的細(xì)節(jié)
??},
??//?在元素被插入到?DOM?前調(diào)用
??beforeMount(el,?binding,?vnode,?prevVnode)?{},
??//?在綁定元素的父組件
??//?及他自己的所有子節(jié)點(diǎn)都掛載完成后調(diào)用
??mounted(el,?binding,?vnode,?prevVnode)?{},
??//?綁定元素的父組件更新前調(diào)用
??beforeUpdate(el,?binding,?vnode,?prevVnode)?{},
??//?在綁定元素的父組件
??//?及他自己的所有子節(jié)點(diǎn)都更新后調(diào)用
??updated(el,?binding,?vnode,?prevVnode)?{},
??//?綁定元素的父組件卸載前調(diào)用
??beforeUnmount(el,?binding,?vnode,?prevVnode)?{},
??//?綁定元素的父組件卸載后調(diào)用
??unmounted(el,?binding,?vnode,?prevVnode)?{},
};
每個(gè)鉤子函數(shù)的參數(shù)包括:
-
el:指令綁定到的元素。可以用于直接操作 DOM。 -
binding:一個(gè)對(duì)象,包含value、oldValue、arg、modifiers、instance、dir屬性。 -
vnode:代表綁定元素的底層 VNode。 -
prevNode:之前的渲染中代表指令所綁定元素的 VNode。僅在beforeUpdate和updated鉤子中可用。
參數(shù)的詳細(xì)介紹,可以查看文檔《Hook Arguments[3]》。
??? 自定義指令分類
1. 按指令注冊(cè)方式分類
自定義指令按「指令注冊(cè)方式」可以分為:「全局指令」和「局部指令」。
- 「全局指令」
全局注冊(cè)的指令可以「在應(yīng)用程序的任何組件中使用」,通常在 Vue 的 app 實(shí)例上通過 directive()進(jìn)行注冊(cè):
const?app?=?createApp({});
app.directive("focus",?{
??//?當(dāng)綁定元素插入到?DOM?中時(shí)......
??mounted(el)?{
????//?聚焦元素
????el.focus();
??},
});
- 「局部指令」
局部注冊(cè)的指令僅「在其注冊(cè)的組件中可用」,通常在組件配置對(duì)象中進(jìn)行注冊(cè):
const?Component?=?defineComponent({
??directives:?{
????focus:?{
??????mounted(el)?{
????????el.focus();
??????},
????},
??},
??render()?{
????const?{?directives?}?=?this.$options;
????return?[withDirectives(h("input"),?[[directives.focus]])];
??},
});
2. 按指令實(shí)現(xiàn)方式分類
自定義指令按「指令實(shí)現(xiàn)方式」可以分為:「對(duì)象指令」和「函數(shù)指令」。
- 「對(duì)象指令 ObjectDirective」
對(duì)象指令以對(duì)象形式實(shí)現(xiàn),提供了更多的選項(xiàng)和生命周期方法:
const?app?=?createApp({});
app.directive("focus",?{
??//?當(dāng)綁定元素插入到?DOM?中時(shí)......
??mounted(el)?{
????//?聚焦元素
????el.focus();
??},
});
在源碼里面接口類型定義如下:
export?interface?ObjectDirective<T?=?any,?V?=?any>?{
??created?:?DirectiveHook<T,?null,?V>;
??beforeMount?:?DirectiveHook<T,?null,?V>;
??mounted?:?DirectiveHook<T,?null,?V>;
??beforeUpdate?:?DirectiveHook<T,?VNode<any,?T>,?V>;
??updated?:?DirectiveHook<T,?VNode<any,?T>,?V>;
??beforeUnmount?:?DirectiveHook<T,?null,?V>;
??unmounted?:?DirectiveHook<T,?null,?V>;
??getSSRProps?:?SSRDirectiveHook;
}
- 「函數(shù)指令 FunctionDirective」
函數(shù)指令是對(duì)象指令的簡(jiǎn)化形式,使用起來更加簡(jiǎn)單,適合于只需執(zhí)行一些操作的場(chǎng)景。
通常僅僅需要在 mounted 和 updated 上實(shí)現(xiàn)相同的行為,除此之外并不需要其他鉤子。這種情況下可以直接用一個(gè)函數(shù)來定義指令,如下所示:
app.directive("color",?(el,?binding)?=>?{
??//?這會(huì)在?`mounted`?和?`updated`?時(shí)都調(diào)用
??el.style.color?=?binding.value;
});
在源碼里面接口類型定義如下:
export?type?FunctionDirective<T?=?any,?V?=?any>?=?DirectiveHook<T,?any,?V>;
export?type?DirectiveHook<T?=?any,?Prev?=?VNode<any,?T>?|?null,?V?=?any>?=?(
??el:?T,
??binding:?DirectiveBinding<V>,
??vnode:?VNode<any,?T>,
??prevVNode:?Prev
)?=>?void;
?? 注意事項(xiàng)
在使用自定義指令時(shí),有一些注意事項(xiàng)需要牢記。這些包括指令命名的規(guī)則、指令的生命周期和鉤子函數(shù)的執(zhí)行順序等。 以下是 5 個(gè)常見注意事項(xiàng):
- 指令需要使用多個(gè)參數(shù)時(shí),可以傳遞一個(gè) JS 對(duì)象字面量
<div?v-demo="{?color:?'white',?text:?'hello!'?}"></div>;
app.directive("demo",?(el,?binding)?=>?{
??console.log(binding.value.color);?//?=>?"white"
??console.log(binding.value.text);?//?=>?"hello!"
});
- 不推薦在組件上使用自定義指令,因?yàn)榻M件可能含有多個(gè)根節(jié)點(diǎn)
和 attribute 不同,指令不能通過 v-bind="$attrs" 來傳遞給一個(gè)不同的元素。
<MyComponent?v-demo="test"?/>
<!--?MyComponent?的模板?-->
<div>
??<!--?v-demo?指令會(huì)被應(yīng)用在此處?-->
??<span>My?component?content</span>
</div>
- 自定義指令第二個(gè)參數(shù)支持一個(gè)對(duì)象配置
定義指令時(shí),第一個(gè)參數(shù)除了指令名稱外,還接受一個(gè)對(duì)象,該對(duì)象包含指令鉤子函數(shù),這與 Vue2 不同,需要注意。
app.directive("focus",?{
??mounted(el)?{
????el.focus();
??},
});
-
在
v-for渲染的元素上,指令鉤子多次調(diào)用
<ul>
<li v-for="item in list" v-focus>
</ul>
focus 指令的鉤子函數(shù)會(huì)以每個(gè) li 元素為參數(shù)調(diào)用多次。
-
v-on修飾符.native不再支持
編輯器會(huì)提示警告“'.native' modifier on 'v-on' directive is deprecated.”
<!-- 會(huì)產(chǎn)生警告, .native 修飾符已廢除 -->
<input @click.native="doSomething">
在 Vue3 中直接使用 @click 即可監(jiān)聽原生事件。
?? 使用示例
接下來以 3 個(gè)使用示例做演示:
v-preview
通過 v-preview 自定義指令,實(shí)現(xiàn)「圖片預(yù)覽功能」。
指令實(shí)現(xiàn):
//?指令實(shí)現(xiàn)
export?default?{
??mounted(el)?{
????el.addEventListener("mouseenter",?(e)?=>?{
??????const?img?=?e.target;
??????const?src?=?img.src;
??????const?parent?=?img.closest(".img-preview-container");
??????parent.style.position?=?"relative";
??????const?preview?=?document.createElement("div");
??????preview.style.position?=?"absolute";
??????preview.style.top?=?0;
??????preview.style.left?=?0;
??????preview.style.background?=?"url("?+?src?+?")?no-repeat?center?center";
??????preview.style.backgroundSize?=?"contain";
??????preview.style.width?=?"100%";
??????preview.style.height?=?"100%";
??????parent.append(preview);
????});
????el.addEventListener("mouseleave",?(e)?=>?{
??????const?parent?=?e.target.closest(".img-preview-container");
??????parent.style.position?=?"";
??????const?preview?=?parent.querySelector("div");
??????preview.remove();
????});
??},
};
注冊(cè)指令:
import?{?createApp?}?from?"vue";
import?vPreview?from?"./directives/vPreview";
import?App?from?"./App.vue";
const?app?=?createApp(App);
//?注冊(cè)指令
app.directive("preview",?vPreview);
app.mount("#app");
使用指令:
<div?class="img-preview-container">
??<img?v-for="src?in?imgSrcs"?:src="src"?v-preview?/>
</div>
當(dāng)鼠標(biāo)移入 img 元素時(shí),會(huì)根據(jù)其 src 展示對(duì)應(yīng)的圖片預(yù)覽。當(dāng)鼠標(biāo)移出時(shí),圖片預(yù)覽會(huì)消失。這個(gè) v-preview 自定義指令可以讓我們快速實(shí)現(xiàn)圖片預(yù)覽的交互效果。
指令中通過監(jiān)聽 mouseenter 和 mouseleave 事件展示和隱藏圖片預(yù)覽,使用 closest 方法獲取 img 元素的父容器,并在其上添加預(yù)覽圖片。
2. v-uppercase
通過 v-uppercase 自定義指令,實(shí)現(xiàn)「將文本自動(dòng)轉(zhuǎn)成大寫功能」。
指令實(shí)現(xiàn):
export?default?{
??created(el,?binding)?{
????el.innerHTML?=?binding.value.toUpperCase();
??},
??update(el,?binding)?{
????el.innerHTML?=?binding.value.toUpperCase();
??},
};
注冊(cè)指令:
import?{?createApp?}?from?"vue";
import?vUppercase?from?"./directives/vUppercase";
import?App?from?"./App.vue";
const?app?=?createApp(App);
//?注冊(cè)指令
app.directive("uppercase",?vUppercase);
app.mount("#app");
使用指令:
<p v-uppercase>hello</p>
在頁(yè)面上顯示的是 “HELLO” 文本。v-uppercase 自定義指令在 created 和 update 鉤子中調(diào)用了 toUpperCase() 方法將文本轉(zhuǎn)換為大寫,并更新 innerHTML。
3. v-resize
通過 v-resize 自定義指令,實(shí)現(xiàn)「監(jiān)聽窗口寬度變化」,執(zhí)行回調(diào)方法的功能。
指令實(shí)現(xiàn):
export?default?{
??mounted(el,?binding)?{
????const?callback?=?binding.value;
????window.addEventListener("resize",?()?=>?{
??????callback(el.offsetWidth);
????});
??},
};
注冊(cè)指令:
import?{?createApp?}?from?"vue";
import?vResize?from?"./directives/vResize";
import?App?from?"./App.vue";
const?app?=?createApp(App);
//?注冊(cè)指令
app.directive("resize",?vResize);
app.mount("#app");
使用指令:
<script setup lang="ts">
const onResize = (width) => {
console.log(width);
};
</script>
<template>
<div v-resize="onResize">寬度</div>
</template>
v-resize 自定義指令會(huì)在窗口尺寸發(fā)生變化時(shí),調(diào)用綁定的回調(diào)函數(shù),并傳入元素的 offsetWidth 值。在方法 onResize 中,我們可以根據(jù)元素的新的寬度 width 進(jìn)行相應(yīng)處理,例如:
- 調(diào)整樣式
- 調(diào)用 API 重新獲取數(shù)據(jù)
- 重新布局頁(yè)面等
這些指令比較簡(jiǎn)單,但在實(shí)際項(xiàng)目中使用卻非常廣泛,我們可以運(yùn)用相同思路編寫其他常用的指令,例如:
-
v-scroll滾動(dòng)事件指令; -
v-mouseenter/v-mouseleave鼠標(biāo)進(jìn)入/離開事件指令; -
v-longpress長(zhǎng)按事件指令;
這可以很好的幫助我們簡(jiǎn)化代碼并提高開發(fā)效率。
??? 渲染函數(shù)中如何使用
1. 概念介紹
如果要在 Vue3 渲染函數(shù)中使用自定義指令,就需要使用 [withDirectives](https://vuejs.org/api/render-function.html#withdirectives "withDirectives")函數(shù),其函數(shù)簽名如下:
function?withDirectives(
??vnode:?VNode,?//?需要綁定自定義指令的元素
??directives:?DirectiveArguments
):?VNode;
//?自定義指令數(shù)組,數(shù)組形式:[Directive,?value,?argument,?modifiers]
//?如果不需要,可以省略數(shù)組的尾元素。
type?DirectiveArguments?=?Array<
??|?[Directive]
??|?[Directive,?any]
??|?[Directive,?any,?string]
??|?[Directive,?any,?string,?DirectiveModifiers]
>;
簡(jiǎn)單的使用示例:
import?{?h,?withDirectives?}?from?"vue";
//?一個(gè)自定義指令
const?pin?=?{
??mounted()?{
????/*?...?*/
??},
??updated()?{
????/*?...?*/
??},
};
//?<div?v-pin:top.animate="200"></div>
const?vnode?=?withDirectives(h("div"),?[[pin,?200,?"top",?{?animate:?true?}]]);
2. 使用示例
以 v-focus 自定義指令為例,可以按照以下步驟實(shí)現(xiàn):
-
導(dǎo)入
withDirectives和自定義指令函數(shù):
import?{?withDirectives?}?from?"vue";
import?{?focus?}?from?"./directives";
-
在渲染函數(shù)中使用
withDirectives函數(shù),并按順序傳遞參數(shù):
const?vnode?=?h("input",?{
??type:?"text",
??modelValue:?"example",
??onInput:?(event)?=>?{
????//?...
??},
});
const?app?=?{
??render()?{
????return?withDirectives(vnode,?[[focus,?true]]);
??},
};
這個(gè)示例代碼中的 vnode 是一個(gè) input 元素的虛擬節(jié)點(diǎn),focus 是 v-focus 自定義指令的函數(shù),true 是傳遞給自定義指令的參數(shù)數(shù)組,表示在元素插入文檔后自動(dòng)聚焦。
?? 總結(jié)
本文介紹了 Vue.js 3.x 中自定義指令的基本使用方法,包括自定義指令函數(shù)的定義和注冊(cè)、指令函數(shù)中的參數(shù)和鉤子函數(shù)等內(nèi)容。自定義指令是 Vue.js 框架的一個(gè)非常重要的擴(kuò)展,開發(fā)者可以根據(jù)自己的需求自定義指令來簡(jiǎn)化開發(fā)工作、提高開發(fā)效率。 希望本文對(duì)您學(xué)習(xí) Vue.js 自定義指令有所幫助。
?? 學(xué)習(xí)資料
以下是一些我個(gè)人認(rèn)為不錯(cuò) Vue3 自定義指令的學(xué)習(xí)資料:
- Vue.js 官方文檔:自定義指令[4]
Vue.js 官方文檔是學(xué)習(xí) Vue.js 自定義指令的最佳入門資料,其中包括了自定義指令的定義、注冊(cè)和鉤子函數(shù)等方面的內(nèi)容,以及一些實(shí)際應(yīng)用的示例。
- Vue Mastery: Vue 3 Custom Directives[5]
Vue Mastery 是一個(gè)非常優(yōu)秀的 Vue.js 在線教育平臺(tái),他們的 Vue 3 Custom Directives 課程是一份非常棒的學(xué)習(xí)資料,其中詳細(xì)介紹了 Vue.js 3.x 中自定義指令的使用方法和實(shí)踐技巧。
- Vue 3 Directives: A Comprehensive Guide In Depth[6]
介紹了 Vue.js 3.x 中指令的使用方法和實(shí)踐技巧。該文章從指令的基礎(chǔ)知識(shí)入手,詳細(xì)介紹了 Vue.js 中內(nèi)置指令和自定義指令的使用方法,并通過實(shí)際應(yīng)用場(chǎng)景和示例來說明指令的作用和用法。
Reference
[1]Vue.js: https://vuejs.org/
[2]自定義指令: https://vuejs.org/guide/reusability/custom-directives.html
[3]Hook Arguments: https://vuejs.org/guide/reusability/custom-directives.html#directive-hooks
[4]Vue.js 官方文檔:自定義指令: https://vuejs.org/guide/reusability/custom-directives.html
[5]Vue Mastery: Vue 3 Custom Directives: https://www.vuemastery.com/courses/vue-3-essentials/custom-directives
[6]Vue 3 Directives: A Comprehensive Guide In Depth: https://www.sciredev.com/blog/vue-3-directives-guide-in-depth
往期回顧
#
如何使用 TypeScript 開發(fā) React 函數(shù)式組件?
# #6 個(gè) Vue3 開發(fā)必備的 VSCode 插件
# #6 個(gè)你必須明白 Vue3 的 ref 和 reactive 問題
# #試著換個(gè)角度理解低代碼平臺(tái)設(shè)計(jì)的本質(zhì)
回復(fù)“加群”,一起學(xué)習(xí)進(jìn)步
