Heim  >  Artikel  >  Web-Frontend  >  So verwenden Sie wiederverwendbare Vue3-Komponenten

So verwenden Sie wiederverwendbare Vue3-Komponenten

PHPz
PHPznach vorne
2023-05-20 19:25:131190Durchsuche

    Vorwort

    Ob Vue oder React: Wenn wir auf mehrere doppelte Codes stoßen, werden wir alle darüber nachdenken, wie das geht Tun Sie es. Verwenden Sie diesen Code erneut, anstatt eine Datei mit einer Menge überflüssigem Code zu füllen.

    Tatsächlich können sowohl Vue als auch React durch Extrahieren von Komponenten wiederverwendet werden. Wenn Sie jedoch auf einige kleine Codefragmente stoßen und keine weitere Datei extrahieren möchten, ist dies relativ. Beispielsweise kann React deklarieren die entsprechenden Widgets in derselben Datei oder implementieren Sie sie über eine Renderfunktion, wie zum Beispiel:

    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;)}
      </>
      )
    }

    Aber für die .vue-Vorlage gibt es keine andere. Sie können sie nicht deklarieren Komponenten in einer einzigen Datei wie React. Wenn Sie Code wiederverwenden möchten, können Sie nur die Komponenten extrahieren. .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>

    So verwenden Sie wiederverwendbare Vue3-Komponenten

    可是,这种方案究竟如何实现呢?毕竟牛都已经吹上天了,实现不了也要硬着头皮折腾。好了,不卖关子,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>

    So verwenden Sie wiederverwendbare Vue3-Komponenten

    真是神奇,那咋实现呢

    上面我们介绍了用法,相信应该没人看不懂,上手成本确实为 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"></component>

    Aber, aber! Genau wie bei der Demo-Komponente oben gibt es hier und da nur zwei oder drei Codezeilen. Wenn Sie es in eine Komponente extrahieren und es nicht für notwendig halten, ist die CV-Methode die einzige Lösung? (Wenn es sich um so etwas wie eine Liste handelt, können Sie natürlich V-for-Code verwenden, aber dies ist nicht das hier vorgestellte Szenario.)

    So verwenden Sie wiederverwendbare Vue3-KomponentenIch weiß, dass Sie es eilig haben, aber seien Sie nicht in Eile noch ängstlich. Wenn wir die Vorlage einkreisen können, die im Rahmen der Komponente wiederverwendet werden soll, sagen Sie Vue, hey, ich habe diesen Code eingekreist, weil ich ihn an mehreren Stellen verwenden muss, obwohl es den Anschein hat, dass Sie diese Funktion derzeit nicht unterstützen , es ist in Ordnung, wenn du es nicht erreichen kannst, werde ich es erreichen

    Der allgemeine Umsetzungsplan sieht so aus:

    <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>

    So verwenden Sie wiederverwendbare Vue3-KomponentenSo verwenden Sie wiederverwendbare Vue3-Komponenten

    Aber wie implementiert man diese Lösung? Schließlich haben Sie sich bereits in den Himmel gerühmt, und wenn Sie es nicht erreichen können, müssen Sie immer noch die Strapazen ertragen. Okay, verraten Sie es nicht, der Antfu-Chef hat es tatsächlich implementiert, genannt createReusableTemplate, und es in VueUse abgelegt. Sie können auf das Dokument klicken, um die Details anzuzeigen.

    Verwendung

    Rufen Sie die Komponenten ab, die Vorlagen definieren und Vorlagen wiederverwenden, über createReusableTemplate

    <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>

    Dann platzieren Sie ihn dort, wo Sie den Code wiederverwenden möchten. Wickeln Sie ihn ein mit DefineTemplate und verwenden Sie es dann an einer beliebigen Stelle in der Einzeldateivorlage über ReuseTemplate: #🎜🎜#
    const [DefineFoo, ReuseFoo] = createReusableTemplate<{ msg: string }>()
    #🎜🎜#⚠️ DefineTemplate muss in ReuseTemplate vorhanden sein Vorher verwendet #🎜🎜#
    #🎜🎜# Wir sehen, dass createReusableTemplate ein Tupel zurückgibt, also ein Paar von Definitions- und Wiederverwendungskomponenten. Anschließend können durch das obige Beispiel mehrere Codes in einer einzigen Datei wiederverwendet werden. . #🎜🎜##🎜🎜#Außerdem können Sie tatsächlich eine Definition zurückgeben und durch Objektdekonstruktion wiederverwenden (das ist erstaunlich, ich werde diesen Artikel nicht näher erläutern. Wenn Sie interessiert sind, werde ich ihn beim nächsten Mal teilen. Die Verwendung ist). das gleiche wie oben. Das Beispiel ist wie folgt#🎜🎜#
    type DefineComponent<PropsOrPropOptions = {}, RawBindings = {}, ...>
    #🎜🎜#„So#🎜🎜##🎜🎜#Es ist wirklich magisch, wie man es implementiert#🎜🎜##🎜🎜#Wir haben die Verwendung oben vorgestellt, ich glaube, niemand kann es verstehen, die Kosten Der Einstieg ist in der Tat 0, dann lasst uns zusammenarbeiten und sehen, wie es geht. #🎜🎜##🎜🎜#Wir wissen, dass Vue3 neben der Verwendung des Skript-Setups auch Komponenten über defineComponent definieren kann. #🎜🎜#
    type ReuseTemplateComponent<
      Bindings extends object,
      Slots extends Record<string, Slot | undefined>,
      /** Bindings使之有类型提示 */
    > = DefineComponent<Bindings> & {
     /** 插槽相关 */
      new(): { $slots: Slots }
    }
    #🎜🎜#Dann beobachten wir, wie die Vorlage definiert wird #🎜🎜#
    type DefineTemplateComponent<
     Bindings extends object,
     Slots extends Record<string, Slot | undefined>,
     Props = {},
    > = DefineComponent<Props> & {
      new(): { $slots: { default(_: Bindings & { $slots: Slots }): any } }
    }
    #🎜🎜# kommt dir bekannt vor? V-Slot?, äh, verdammt, ist das nicht ein Slot! Außerdem scheint der Vorlagencode im Standardsteckplatz platziert zu sein. #🎜🎜##🎜🎜#Okay, schauen wir uns an, wie man die Define-Funktion implementiert. #🎜🎜##🎜🎜#Implementieren von Define#🎜🎜##🎜🎜#Wir haben gerade gesagt, dass die Vorlage im Standard-Slot definiert ist, dann können wir ein lokales Variablenrendering definieren, das eingegeben wird, wenn Define im verwendet wird Template-Setup, wäre es zu diesem Zeitpunkt nicht gut, sich „slot.default“ zu besorgen und es auf „Rendern“ zu stellen? , der Code lautet wie folgt#🎜🎜#
      const define = defineComponent({
        props {
          name: String
        }
      })
      const reuse = defineComponent({
        props {
          name: String
        }
      })
    #🎜🎜#Ja, so einfach ist das. Für Define besteht der Kerncode nur aus diesen zwei oder drei Zeilen#🎜🎜##🎜🎜#Implementierung der Wiederverwendung#🎜🎜##🎜 🎜# Wir haben oben die Renderfunktion erhalten. Wenn wir also Wiederverwendung verwenden, wäre es dann nicht gut, den erhaltenen V-Slot, die Attrs usw. zum Rendern zu übergeben? #🎜🎜##🎜🎜#In ähnlicher Weise geben wir bei Verwendung der Wiederverwendung in der Vorlage das Setup ein, übergeben dann alle Parameter zum Rendern und geben das Ergebnis des Renderns zurück.#🎜🎜#
    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 })
          }
        },
      })
    #🎜🎜# Die oben genannten Attribute sind die Requisiten, die Sie in Reuse #🎜🎜#rrreee#🎜🎜# hochgeladen haben, und warum müssen Sie $slots übergeben? #🎜🎜##🎜🎜#Oben gibt es tatsächlich ein Beispiel. In der Vorlage können Sie die Einfügung auch über die dynamische Komponente <component :is="$slots.default"></component>erhalten > Der eigentliche Inhalt des Slots#🎜🎜#rrreee#🎜🎜##🎜🎜##🎜🎜##🎜🎜#Natürlich sind nicht nur der Standard-Slot, sondern auch andere benannte Slots verfügbar#🎜🎜#rrreee# 🎜🎜##🎜 🎜##🎜🎜##🎜🎜#Es liegt an Ihnen, zu entscheiden, wie Sie die Blume spielen ~#🎜🎜##🎜🎜#Typunterstützung, Entwicklungserfahrung verbessern#🎜🎜##🎜🎜#Wir Definieren Sie die Vorlage, aber die Vorlage empfängt. Sie müssen mir mitteilen, welche Parameter und welche Parameter übergeben werden. Wenn keine Eingabeaufforderung vorhanden ist, ist die Entwicklungserfahrung äußerst schlecht. Aber keine Sorge, der Chef hat diese bereits berücksichtigt . #🎜🎜#

    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 具有更好的语义化So verwenden Sie wiederverwendbare Vue3-Komponenten

    So verwenden Sie wiederverwendbare Vue3-Komponenten

    也就是说,我不想多次调用 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 })
          }
        },
      })

    Das obige ist der detaillierte Inhalt vonSo verwenden Sie wiederverwendbare Vue3-Komponenten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Stellungnahme:
    Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen