Maison  >  Article  >  interface Web  >  Comment utiliser les composants réutilisables Vue3

Comment utiliser les composants réutilisables Vue3

PHPz
PHPzavant
2023-05-20 19:25:131276parcourir

    Avant-propos

    Que ce soit vue ou réagir, lorsque nous rencontrons plusieurs codes en double, nous réfléchirons à la manière de réutiliser ces codes, au lieu de remplir un fichier avec un tas de codes redondants.

    En fait, Vue et React peuvent réaliser une réutilisation en extrayant des composants, mais si vous rencontrez de petits fragments de code et que vous ne souhaitez pas extraire un autre fichier, en comparaison, React peut déclarer les petits composants correspondants dans le même fichier, ou implémentez-le via la fonction de rendu, telle que :

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

    Mais pour le modèle .vue, il n'y a aucun moyen de déclarer d'autres composants dans un seul fichier comme réagir. ne peut être effectué qu'en extrayant des composants. .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>

    Comment utiliser les composants réutilisables 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>

    Comment utiliser les composants réutilisables 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"></component>

    Mais, mais, mais ! Tout comme le composant Demo ci-dessus, il n'y a que deux ou trois lignes de code ici et là. Si vous l'extrayez en un seul composant et que vous ne pensez pas que cela soit nécessaire, alors la seule solution est la méthode CV ? (Bien sûr, s'il s'agit de quelque chose comme une liste, vous pouvez utiliser v-for code, mais ce n'est pas le scénario présenté ici)

    Comment utiliser les composants réutilisables Vue3Je sais que vous êtes pressé, mais ne vous inquiétez pas encore. Si nous pouvons encercler le modèle à réutiliser dans le cadre du composant, dites à Vue, hé, j'ai encerclé ce code car je dois l'utiliser à plusieurs endroits, même s'il semble que vous ne supportiez pas cette fonction pour le moment. , c'est bon, si vous n'y parvenez pas, je le mettrai en œuvre

    Le plan approximatif de mise en œuvre est le suivant :

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

    Comment utiliser les composants réutilisables Vue3Comment utiliser les composants réutilisables Vue3

    Mais comment mettre en œuvre cette solution ? Après tout, vous vous êtes déjà vanté jusqu'au ciel, et si vous n'y parvenez pas, vous devez encore endurer les épreuves. D'accord, ne le donnez pas, le patron d'antfu l'a en fait implémenté, appelé createReusableTemplate, et l'a mis dans VueUse. Vous pouvez cliquer sur le document pour voir les détails.

    Utilisation

    Obtenez les composants qui définissent les modèles et réutilisez les modèles via 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>

    Ensuite, là où vous souhaitez réutiliser le code, enveloppez-le avec DefineTemplate, puis utilisez ReuseTemplate est utilisé n'importe où dans le modèle à fichier unique : 🎜
    const [DefineFoo, ReuseFoo] = createReusableTemplate<{ msg: string }>()
    🎜⚠️ DefineTemplate doit être utilisé avant ReuseTemplate🎜
    🎜Nous voyons que createReusableTemplate renvoie un Tuple, c'est-à-dire définir et réutiliser des paires de composants, puis, via Dans l'exemple ci-dessus, plusieurs codes peuvent être réutilisés dans un seul fichier. 🎜🎜De plus, vous pouvez effectivement renvoyer une définition et la réutiliser via la déconstruction d'objets (c'est incroyable, je ne développerai pas cet article, je le partagerai la prochaine fois si vous êtes intéressé), l'utilisation est la même que ci-dessus, l'exemple est le suivant🎜
    type DefineComponent<PropsOrPropOptions = {}, RawBindings = {}, ...>
    🎜🎜🎜C'est vraiment incroyable, comment l'implémenter🎜 🎜Nous avons introduit l'utilisation ci-dessus. Je pense que personne ne peut le comprendre. Le coût de démarrage est en effet de 0, alors voyons comment l'implémenter. 🎜🎜Nous savons qu'en plus de la configuration du script, Vue3 peut également définir des composants via defineComponent🎜
    type ReuseTemplateComponent<
      Bindings extends object,
      Slots extends Record<string, Slot | undefined>,
      /** Bindings使之有类型提示 */
    > = DefineComponent<Bindings> & {
     /** 插槽相关 */
      new(): { $slots: Slots }
    }
    🎜Observons ensuite comment le modèle est défini🎜
    type DefineTemplateComponent<
     Bindings extends object,
     Slots extends Record<string, Slot | undefined>,
     Props = {},
    > = DefineComponent<Props> & {
      new(): { $slots: { default(_: Bindings & { $slots: Slots }): any } }
    }
    🎜Ça vous semble familier ? v-slot ?, eh, putain, c'est pas une slot ! De plus, le code du modèle semble être placé dans l'emplacement par défaut. 🎜🎜D'accord, voyons comment implémenter la fonction Définir. 🎜🎜Implémentation de Define🎜🎜Nous venons de dire que le modèle est défini dans l'emplacement par défaut, nous pouvons alors définir un rendu de variable locale plus tard, lors de l'utilisation de Define dans le modèle, il entrera dans la configuration. slot.default et mettez-le dans Ne serait-il pas préférable de simplement restituer ? , le code est le suivant🎜
      const define = defineComponent({
        props {
          name: String
        }
      })
      const reuse = defineComponent({
        props {
          name: String
        }
      })
    🎜Oui, c'est aussi simple que cela. Pour Define, le code de base n'est que ces deux ou trois lignes🎜🎜implémentant Reuse🎜🎜La fonction de rendu est obtenue ci-dessus, puis là où nous utilisons Reuse, nous le ferons. get Ne serait-il pas bon de transmettre le v-slot, les attributs, etc. pour le rendu ? 🎜🎜De même, lorsque nous utilisons Reuse dans le modèle, nous entrerons dans la configuration, puis transmettrons tous les paramètres pour le rendu et renverrons le résultat du rendu🎜
    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 })
          }
        },
      })
    🎜Les attributs ci-dessus sont les accessoires que vous avez téléchargés dans Reuse 🎜rrreee🎜Et pourquoi faut-il passer $slots ? 🎜🎜Il y a en fait un exemple ci-dessus. Dans le modèle, vous pouvez également obtenir le contenu réel du slot via le composant dynamique <component :is="$slots.default"></component>🎜 rrreee 🎜🎜🎜🎜Bien sûr, non seulement les emplacements par défaut, mais également d'autres emplacements nommés sont disponibles🎜rrreee🎜🎜🎜🎜C'est à vous de décider comment jouer aux fleurs~🎜🎜Tapez le support pour améliorer l'expérience de développement🎜🎜 Nous avons défini des modèles, mais vous devez me dire quels paramètres le modèle reçoit et quels paramètres sont transmis. S'il n'y a pas d'invite de type, l'expérience de développement sera extrêmement mauvaise. Cependant, ne vous inquiétez pas, le patron a déjà réfléchi. ces. 🎜

    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 具有更好的语义化Comment utiliser les composants réutilisables Vue3

    Comment utiliser les composants réutilisables 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 })
          }
        },
      })

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Déclaration:
    Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer