您好,登錄后才能下訂單哦!
這篇文章主要講解了“Vue3復用組件怎么使用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Vue3復用組件怎么使用”吧!
無論是 vue 還是 react,當遇到多處重復代碼的時候,我們都會想著如何復用這些代碼,而不是一個文件里充斥著一堆冗余代碼。
實際上,vue 和 react 都可以通過抽組件的方式來達到復用,但如果遇到一些很小的代碼片段,你又不想抽到另外一個文件的情況下,相比而言,react 可以在相同文件里面聲明對應的小組件,或者通過 render function 來實現,如:
const Demo: FC<{ msg: string}> = ({ msg }) => { return <div>demo msg is { msg } </div> } const App: FC = () => { return ( <> <Demo msg="msg1"/> <Demo msg="msg2"/> </> ) }
/** render function的形式 */ const App: FC = () => { const renderDemo = (msg : string) => { return <div>demo msg is { msg } </div> } return ( <> {renderDemo('msg1')} {renderDemo('msg2')} </> ) }
但對于 .vue
模板 來說,沒法像 react 一樣在單個文件里面聲明其他組件,如果你要復用代碼,那就只能通過抽離組件的方式。
可是啊可是啊!就如上面 Demo 組件,就零零散散兩三行代碼,抽成一個組件你又覺得沒太必要,那唯一的解決方案就是 CV 大法?(當然,如果是諸如 list 之類的,可以用 v-for 代碼,但這里介紹的不是這種場景)
我知道你很急,但你先別急。如果我們可以通過在組件范圍內將要復用的模板圈起來,跟 vue 說,喂,這代碼是我圈起來的,因為我有好幾處要用到,雖然目前你看起來好像不支持這功能,不過,沒事,你實現不了的,我來實現
那大致實現方案就是這樣子啦:
<template> <DefineFoo v-slot="{ msg }"> <div>Foo: {{ msg }}</div> </DefineFoo> ... <ReuseFoo msg="msg1" /> <div>xxx</div> <ReuseFoo msg="msg2" /> <div>yyy</div> <ReuseFoo msg="msg3" /> </template>
可是,這種方案究竟如何實現呢?畢竟牛都已經吹上天了,實現不了也要硬著頭皮折騰。好了,不賣關子,antfu 大佬實際上已經實現了,叫做createReusableTemplate
,且放到 VueUse 里面了,具體可以點擊文檔看看。
通過 createReusableTemplate 拿到定義模板和復用模板的組件
<script setup> import { createReusableTemplate } from '@vueuse/core' const [DefineTemplate, ReuseTemplate] = createReusableTemplate() </script>
然后在你要復用代碼的地方,將其用DefineTemplate
包起來,之后就可以通過ReuseTemplate
在單文件 template 的任意地方使用了:
<template> <DefineTemplate> <!-- 這里定義你要復用的代碼 --> </DefineTemplate> <!-- 在你要復用的地方使用ReuseTemplate, --> <ReuseTemplate /> ... <ReuseTemplate /> </template>
?? DefineTemplate 務必在 ReuseTemplate 之前使用
我們看到,createReusableTemplate 返回了一個 Tuple,即 define 和 reuse 的組件對,然后,通過上面的例子就可以在單文件里面復用多處代碼了。
還有,實際上還可以通過對象解構的方式返回一個 define 和 reuse(很神奇吧,這篇文章就不展開了,有興趣下次再分享下),用法同上,例子如下
<script setup lang="ts"> const [DefineFoo, ReuseFoo] = createReusableTemplate<{ msg: string }>() const TemplateBar = createReusableTemplate<{ msg: string }>() const [DefineBiz, ReuseBiz] = createReusableTemplate<{ msg: string }>() </script> <template> <DefineFoo v-slot="{ msg }"> <div>Foo: {{ msg }}</div> </DefineFoo> <ReuseFoo msg="world" /> <!-- 看這里 --> <TemplateBar.define v-slot="{ msg }"> <div>Bar: {{ msg }}</div> </TemplateBar.define> <TemplateBar.reuse msg="world" /> <!-- Slots --> <DefineBiz v-slot="{ msg, $slots }"> <div>Biz: {{ msg }}</div> <component :is="$slots.default" /> </DefineBiz> <ReuseBiz msg="reuse 1"> <div>This is a slot from Reuse</div> </ReuseBiz> <ReuseBiz msg="reuse 2"> <div>This is another one</div> </ReuseBiz> </template>
上面我們介紹了用法,相信應該沒人看不懂,上手成本確實為 0,那接下來我們一起看看是如何實現的。
我們知道,Vue3 定義組件除了通過 script setup 的方式之外, 還可以通過defineComponent
的方式
const Demo = defineComponent({ props: { ..., }, setup(props, { attrs, slots }) { return () => { ... } } })
然后我們觀察下是如何定義模板的
<DefineFoo v-slot="{ msg }"> <div>Foo: {{ msg }}</div> </DefineFoo>
好像似曾相識?v-slot?,誒,臥槽,這不是插槽嗎!還有,模板代碼看起來就是放在默認插槽的。
好,我們接下來看下如何實現 Define 的功能。
我們剛才說,模板是定義在默認插槽里面,那我們可以定義個局部變量 render,之后當在 template 里面使用 Define 時會進入 setup,這時候拿到 slot.default,放在 render 上不就好?,代碼如下
let render: Slot | undefined const Define = defineComponent({ setup(props, { slots, }) { return () => { /** 這里拿到默認插槽的渲染函數 */ render = slots.default } } })
對的,就是這么簡單,對于 Define 來說,核心代碼就是這兩三行而已
上面拿到 render function 了,那我們在使用 Reuse 的地方,將所得到的 v-slot、attrs 等傳給 render 不就好?
同樣,當我們在 template 使用 Reuse 的時候,就會進入 setup,然后將得到的參數都傳給 render,并 return render 的結果即可
const reuse = defineComponent({ setup(_, { attrs, slots }) { return () => { /** * 沒有render,有兩種可能 * 1. 你沒Define * 2. Define寫在Reuse后面 **/ if (!render && process.env.NODE_ENV !== 'production') throw new Error(`[vue-reuse-template] Failed to find the definition of template${name ? ` "${name}"` : ''}`) return render?.({ ...attrs, $slots: slots }) } }, })
上面的 attrs 也就是你在 Reuse 上傳的 prop 了
<ReuseFoo msg="msg1" />
而為啥還要傳個$slots?
上面實際上有個例子,模板里面還可以通過動態組件<component :is="$slots.default" />
的方式來拿到插槽的真正內容
<DefineBiz v-slot="{ msg, $slots }"> <div>Biz: {{ msg }}</div> <component :is="$slots.default" /> </DefineBiz> <ReuseBiz msg="reuse 1"> <div>This is a slot from Reuse</div> </ReuseBiz> <ReuseBiz msg="reuse 2"> <div>This is another one</div> </ReuseBiz>
當然,不只默認插槽啦,其他具名插槽都可以
<DefineBiz v-slot="{ msg, $slots }"> <component :is="$slots.header" /> <div>Biz: {{ msg }}</div> <component :is="$slots.default" /> </DefineBiz> <ReuseBiz msg="reuse 1"> <template #header> <div>我是 reuse1的header</div> </template> <div>This is a slot from Reuse</div> </ReuseBiz> <ReuseBiz msg="reuse 2"> <template #header> <div>我是 reuse1的header</div> </template> <div>This is another one</div> </ReuseBiz>
具體怎么玩出花,你來定~
我們定義了模板,但模板接收什么參數,傳入什么參數,你得告訴我對不對,如果沒有類型的提示,那么開發體驗會極其糟糕,不過,不用擔心,大佬這些都考慮好了。
createReusableTemplate 支持泛型參數,也就是說你要復用的模板需要什么參數,只需要通過傳入對應類型即可,比如你有個 msg,是 string 類型,那么用法如下
const [DefineFoo, ReuseFoo] = createReusableTemplate<{ msg: string }>()
然后你就會發現,DefineFoo, ReuseFoo 都會對應的類型提示了
我們上面說是用 defineComponent 得到 Define 和 Reuse,而 defineComponent 返回的類型就是 DefineComponent 呀
type DefineComponent<PropsOrPropOptions = {}, RawBindings = {}, ...>
假設模板參數類型為 Bindings 的話,那么對于 Reuse 來說,其既支持傳參,也支持添加插槽內容,所以類型如下
type ReuseTemplateComponent< Bindings extends object, Slots extends Record<string, Slot | undefined>, /** Bindings使之有類型提示 */ > = DefineComponent<Bindings> & { /** 插槽相關 */ new(): { $slots: Slots } }
而對于 Define 類型來說,我們知道其 v-slot 也有對應的類型,且能通過$slots 拿到插槽內容,所以類型如下
type DefineTemplateComponent< Bindings extends object, Slots extends Record<string, Slot | undefined>, Props = {}, > = DefineComponent<Props> & { new(): { $slots: { default(_: Bindings & { $slots: Slots }): any } } }
ok,相信我開頭說的看懂只需要 1 分鐘不到應該不是吹的,確實實現很簡單,但功能又很好用,解決了無法在單文件復用代碼的問題。
我們來小結一下:
通過 Define 來將你所需要復用的代碼包起來,通過 v-slot 拿到傳過來的參數,同時支持渲染其他插槽內容
通過 Reuse 來復用代碼,通過傳參渲染出不同的內容
為了提升開發體驗,加入泛型參數,所以 Define 和 Reuse 都有對應的參數類型提示
要記住使用條件,Define 在上,Reuse 在下,且不允許只使用 Reuse,因為拿不到 render function,所以會報錯
實際上多次調用 createReusableTemplate 得到相應的 DefineXXX、ReuseXXX 具有更好的語義化)
也就是說,我不想多次調用 createReusableTemplate 了,直接讓 define 和 reuse 支持 name 參數(作為唯一的 template key),只要兩者都有相同的 name,那就意味著它們是同一對
實際上也很簡單,既然要支持 prop name
來作為唯一的 template key,那 define 和 reuse 都添加 prop name 不就好?
const define = defineComponent({ props { name: String } }) const reuse = defineComponent({ props { name: String } })
然后之前不是有個 render 局部變量嗎?因為現在要讓一個 Define 支持通過 name 來區分不同的模板,那么我們判斷到傳入了 name,就映射對應的的 render 不就好?
這里我們通過 Map 的方式存儲不同 name 對應的 render,然后 define setup 的時候存入對應 name 的 render,reuse setup 的時候通過 name 拿到對應的 render,當然如果沒傳入 name,默認值是 default,也就是跟沒有魔改之前是一樣的
const renderMap = new Map<string, Slot | undefined>() const define = defineComponent({ props: { /** template name */ name: String, }, setup(props, { slots }) { return () => { const templateName: string = props.name || 'default' if (!renderMap.has(templateName)) { // render = slots.default renderMap.set(templateName, slots.default) } } }, }) const reuse = defineComponent({ inheritAttrs: false, props: { name: String, }, setup(props, { attrs, slots }) { return () => { const templateName: string = props.name || 'default' const render = renderMap.get(templateName) if (!render && process.env.NODE_ENV !== 'production') throw new Error(`[vue-reuse-template] Failed to find the definition of template${templateName}`) return render?.({ ...attrs, $slots: slots }) } }, })
感謝各位的閱讀,以上就是“Vue3復用組件怎么使用”的內容了,經過本文的學習后,相信大家對Vue3復用組件怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。