Vue3自定義指令的實踐示例
關(guān)鍵接口介紹
最近想體驗下自定義指令功能,看了看文檔和 vue2 差異不大,語法如下:
const?myDirective?=?{ //?在綁定元素的?attribute?前? //?或事件監(jiān)聽器應(yīng)用前調(diào)用 created(el,?binding,?vnode,?prevVnode)? {?//?下面會介紹各個參數(shù)的細(xì)節(jié)?},? //?在元素被插入到?DOM?前調(diào)用 beforeMount(el,?binding,?vnode,?prevVnode)?{}, //?在綁定元素的父組件 //?及他自己的所有子節(jié)點都掛載完成后調(diào)用 mounted(el,?binding,?vnode,?prevVnode)?{}, //?綁定元素的父組件更新前調(diào)用 beforeUpdate(el,?binding,?vnode,?prevVnode)?{}, //?在綁定元素的父組件 //?及他自己的所有子節(jié)點都更新后調(diào)用 updated(el,?binding,?vnode,?prevVnode)?{}, //?綁定元素的父組件卸載前調(diào)用 beforeUnmount(el,?binding,?vnode,?prevVnode)?{}, //?綁定元素的父組件卸載后調(diào)用 unmounted(el,?binding,?vnode,?prevVnode)?{} }
起初,最大的痛點是需要手動創(chuàng)建 dom ,然后插入 el 中。
const?loadingDom?=?document.createElement('div',?{calss:?'loading'}) el.append(loadingDom)
這樣好難受啊,我不想寫原生 dom ,能不能寫個組件渲染到指令里呢?
我想起了我之前看到的幾個 vue 接口,
- h函數(shù),也就是 vue 提供的創(chuàng)建 vNode 的函數(shù)
- render函數(shù):將 vNode 渲染到 真實 dom 里的函數(shù)
h函數(shù)用法如下:
//?完整參數(shù)簽名 function?h( ????type:?string?|?Component, ????props?:?object?|?null, ????children?:?Children?|?Slot?|?Slots ):?VNode
例如:
import?{?h?}?from?'vue' const?vnode?=?h('div',?{?class:?'container'?},?[ ??h('h1',?'Hello,?Vue?3'), ??h('p',?'This?is?a?paragraph') ])
我們使用h函數(shù)創(chuàng)建了一個 VNode,它表示一個包含 div、h1、p 的 DOM 結(jié)構(gòu)。其中,div 的 class 屬性為 container
自定義 loading 組件
然而,當(dāng)我使用 props 為組件傳遞值時,發(fā)現(xiàn)是徒勞的。
import?Loading?from?'./components/Loading.vue'; cconst?option?=?{ ????msg:?'一大波僵尸來襲', ????loading:?true } const?vnode?=?h( ????Loading, ????{?id:?'loading',?...option} )
僅僅如下圖一樣,我以為是給組件的 props,實際上是 dom 的 props。
此時我們的組件只能通過 $attrs
來獲取這些 props 了,如下:
<template> ??<div?class="loading"> ????<div></div> ????<span?v-if="$attrs.msg?!==?false">{{?$attrs.msg?}}</span> ??</div> </template>
接著我們給組件實現(xiàn) loading 動畫,當(dāng)然你也可以直接使用組件庫的 loading 組件。
我的實現(xiàn)如下:
<style> ??@keyframes?identifier?{ ????100%?{ ??????-webkit-transform:?rotate(360deg); ??????transform:?rotate(360deg); ????} ??} ??.loading?{ ????height:?100px; ????width:?100%; ??} ??.loading?div?{ ????width:?50px; ????height:?50px; ????border-radius:?50%; ????border:?2px?solid?green; ????margin:?25px?auto; ????border-top:?none; ????border-left:?none; ????animation:?identifier?1s?infinite?linear; ??} </style>
自定義指令
接下來我們封裝自定義指令。
我們的思路是:
- mounted 階段,如果是 true,那么渲染組件,否則什么都不做。
- update 階段,如果 true 則重新渲染組件,如果 false 則渲染 vnode
為了可以應(yīng)對更多場景,我們期望可以配置加載中的提示信息,不配置使用默認(rèn)值,如果是 false ,那么僅展示 loading 圖。所以參數(shù)類型如下:
interface?Props??{loading:?boolean,?msg?:?string?|?false} v-loading:?boolean?|?Props
由于可能是布爾值,也可能是對象,我們需要初始化配置參數(shù)
function?formatOption?(value:?boolean?|?Props)?{ ??const?loading?=?typeof?value?===?'boolean' ????????value? ??????:?value.loading ??const?option?=?typeof?value?!==?'boolean' ??????Object.assign(defaultOption,?value) ????:?{ ??????loading, ??????...defaultOption ????} ??return?{?loading,?option?} }
接著再 mounted 階段獲取格式化后的 loading 和 option,如果為 true 則直接渲染組件。
const?vLoading:?Directive<HTMLElement,?boolean?|?Props>?=?{ ??mounted(el,?binding)?{ ????const?{?loading,?option?}?=?formatOption(binding.value) ????loading?&&?renderLoading(el,?option) ??} } function?renderLoading?(el:?HTMLElement,?option:?Props)?{ ??const?vnode?=?h( ????Loading, ????{?id:?'loading',?...option} ??) ??el.removeChild(el.children[0]) ??render(vnode,?el) }
如果進(jìn)入 update 階段,則根據(jù)情況選擇渲染 laoding 組件還是 vnode。
const?vLoading:?Directive<HTMLElement,?boolean?|?Props>?=?{ ??mounted(el,?binding)?{ ????const?{?loading,?option?}?=?formatOption(binding.value) ????loading?&&?renderLoading(el,?option) ??}, ??updated(el:?HTMLElement,?binding,?vnode)?{ ????const?{?loading,?option?}?=?formatOption(binding.value) ????if?(loading)?{ ??????renderLoading(el,?option) ????}?else?{ ??????renderVNode(el,?vnode) ????} ??}, } function?renderLoading?(el:?HTMLElement,?option:?Props)?{ ??const?vnode?=?h( ????Loading, ????{?id:?'loading',?...option} ??) ??el.removeChild(el.children[0]) ??render(vnode,?el) } function?renderVNode?(el:?HTMLElement,?vnode:?VNode)?{ ??el.querySelector('#loading')?.remove() ??render(vnode,?el) }
我們驗證下功能:
1.默認(rèn)功能
const?loading?=?ref(true) setTimeout(()?=>?loading.value?=?false,?2000) </script> <template> ??<div?style="width:?300px"?v-loading=laoding> ????<h1>加載成功</h1> ??</div> </template>
2.自定義文本
<template> ??<div?style="width:?300px"?v-loading="{?loading,?msg:?'一大波僵尸來襲'?}"> ????<h1>加載成功</h1> ??</div> </template>
3.不展示文本
<template> ??<div?style="width:?300px"?v-loading="{?loading,?msg:?false?}"> ????<h1>加載成功</h1> ??</div> </template>
最后
今天的分享就到這了,如果有問題,可以評論區(qū)告訴我,我會及時更正。
以下是完整的代碼。
<template> ??<div?class="loading"> ????<div></div> ????<span?v-if="$attrs.msg?!==?false">{{?$attrs.msg?}}</span> ??</div> </template> <script?lang="ts"> export?default?{?? } </script> <style> ??@keyframes?identifier?{ ????100%?{ ??????-webkit-transform:?rotate(360deg); ??????transform:?rotate(360deg); ????} ??} ??.loading?{ ????height:?100px; ????width:?100%; ??} ??.loading?div?{ ????width:?50px; ????height:?50px; ????border-radius:?50%; ????border:?2px?solid?green; ????margin:?25px?auto; ????border-top:?none; ????border-left:?none; ????animation:?identifier?1s?infinite?linear; ??} </style>
<script?setup?lang="ts"> import?{?Directive,?VNode,?h,?ref,?render??}?from?'vue'; import?Loading?from?'./components/Loading.vue'; const?defaultOption:?{msg?:?string?|?false}?=?{ ??msg:?'努力加載中' } interface?Props??{loading:?boolean,?msg?:?string?|?false} function?formatOption?(value:?boolean?|?Props)?{ ??const?loading?=?typeof?value?===?'boolean' ????????value? ??????:?value.loading ??const?option?=?typeof?value?!==?'boolean' ??????Object.assign(defaultOption,?value) ????:?{ ??????loading, ??????...defaultOption ????} ??return?{?loading,?option?} } function?renderLoading?(el:?HTMLElement,?option:?Props)?{ ??const?vnode?=?h( ????Loading, ????{?id:?'loading',?...option} ??) ??el.removeChild(el.children[0]) ??render(vnode,?el) } function?renderVNode?(el:?HTMLElement,?vnode:?VNode)?{ ??el.querySelector('#loading')?.remove() ??render(vnode,?el) } const?vLoading:?Directive<HTMLElement,?boolean?|?Props>?=?{ ??mounted(el,?binding)?{ ????const?{?loading,?option?}?=?formatOption(binding.value) ????loading?&&?renderLoading(el,?option) ??}, ??updated(el:?HTMLElement,?binding,?vnode)?{ ????const?{?loading,?option?}?=?formatOption(binding.value) ????if?(loading)?{ ??????renderLoading(el,?option) ????}?else?{ ??????renderVNode(el,?vnode) ????} ??}, } const?loading?=?ref(true) setTimeout(()?=>?loading.value?=?false,?2000) </script> <template> ??<div?style="width:?300px"?v-loading="{?loading,?msg:?'一大波僵尸來襲'?}"> ????<h1>加載成功</h1> ??</div> </template>
以上就是Vue3自定義指令的實踐示例的詳細(xì)內(nèi)容,更多關(guān)于Vue3自定義指令的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue 設(shè)置axios請求格式為form-data的操作步驟
今天小編就為大家分享一篇Vue 設(shè)置axios請求格式為form-data的操作步驟,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-10-10vue3中使用vuex和vue-router的詳細(xì)步驟
這篇文章主要介紹了vue3中使用vuex和vue-router的步驟,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12Vue實現(xiàn)Tab標(biāo)簽路由效果并用Animate.css做轉(zhuǎn)場動畫效果的代碼
這篇文章主要介紹了Vue實現(xiàn)Tab標(biāo)簽路由效果,并用Animate.css做轉(zhuǎn)場動畫效果,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07vue2實現(xiàn)可復(fù)用的輪播圖carousel組件詳解
這篇文章主要為大家詳細(xì)介紹了vue2實現(xiàn)可復(fù)用的輪播圖carousel組件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11vue-class-setup?編寫?class?風(fēng)格組合式API
這篇文章主要為大家介紹了vue-class-setup?編寫?class?風(fēng)格組合式API,支持Vue2和Vue3,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09