首頁 > web前端 > Vue.js > 主體

Vue3復用元件怎麼使用

PHPz
發布: 2023-05-20 19:25:13
轉載
1281 人瀏覽過

    前言

    無論是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(&#39;msg1&#39;)}
        {renderDemo(&#39;msg2&#39;)}
      </>
      )
    }
    登入後複製

    但對於.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>
    登入後複製

    Vue3復用元件怎麼使用

    可是,這個方案究竟如何實現呢?畢竟牛都已經吹上天了,實現不了也要硬著頭皮折騰。好了,不賣關子,antfu 大佬其實已經實現了,叫做createReusableTemplate#,且放到 VueUse 裡面了,具體可以點擊文檔看看。

    用法

    透過createReusableTemplate 拿到定義範本和重複使用範本的元件

    <script setup>
    import { createReusableTemplate } from &#39;@vueuse/core&#39;
    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>
    登入後複製

    Vue3復用元件怎麼使用

    真是神奇,那咋實現呢

    上面我們介紹了用法,相信應該沒人看不懂,上手成本確實為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 的功能。

    實作Define

    我們剛才說,模板是定義在預設插槽裡面,那我們可以定義個局部變數render,之後當在template 裡面使用Define 時會進入setup,這時候拿到slot.default,放在render 上不就好? ,程式碼如下

    let render: Slot | undefined
    const Define = defineComponent({
      setup(props, { slots, }) {
        return () => {
          /** 这里拿到默认插槽的渲染函数 */
          render = slots.default
        }
      }
    })
    登入後複製

    對的,就是這麼簡單,對於Define 來說,核心程式碼就是這兩三行而已

    實作Reuse

    上面拿到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 !== &#39;production&#39;)
              throw new Error(`[vue-reuse-template] Failed to find the definition of template${name ? ` "${name}"` : &#39;&#39;}`)
            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>
    登入後複製

    Vue3復用元件怎麼使用

    當然,不只預設插槽啦,其他具名插槽都可以

    <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>
    登入後複製

    Vue3復用元件怎麼使用

    具體怎麼玩出花,你來定~

    類型支持,提升開發體驗

    我們定義了模板,但模板接收什麼參數,傳入什麼參數,你得告訴我對不對,如果沒有類型的提示,那麼開發體驗會極其糟糕,不過,不用擔心,大佬這些都考慮好了。

    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 具有更好的语义化Vue3復用元件怎麼使用

    Vue3復用元件怎麼使用

    也就是说,我不想多次调用 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 || &#39;default&#39;
            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 || &#39;default&#39;
            const render = renderMap.get(templateName)
            if (!render && process.env.NODE_ENV !== &#39;production&#39;)
              throw new Error(`[vue-reuse-template] Failed to find the definition of template${templateName}`)
            return render?.({ ...attrs, $slots: slots })
          }
        },
      })
    登入後複製

    以上是Vue3復用元件怎麼使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    相關標籤:
    來源:yisu.com
    本網站聲明
    本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
    熱門教學
    更多>
    最新下載
    更多>
    網站特效
    網站源碼
    網站素材
    前端模板