>  기사  >  웹 프론트엔드  >  Vue3 재사용 가능한 구성 요소를 사용하는 방법

Vue3 재사용 가능한 구성 요소를 사용하는 방법

PHPz
PHPz앞으로
2023-05-20 19:25:131191검색

    머리말

    vue든 React든, 중복된 코드가 여러 개 발견되면 파일을 중복된 코드로 채우는 대신 이러한 코드를 재사용하는 방법에 대해 생각해 보겠습니다.

    사실 vue와 React 모두 구성 요소를 추출하여 재사용을 달성할 수 있지만 작은 코드 조각이 발견되어 다른 파일을 추출하고 싶지 않은 경우 이에 비해 React는 동일한 파일에서 해당 작은 구성 요소를 선언할 수 있습니다. 또는 다음과 같은 렌더링 기능을 통해 구현합니다.

    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처럼 단일 파일에 다른 구성 요소를 선언할 수 있는 방법이 없습니다. 구성 요소를 추출해야만 수행할 수 있습니다. .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"></component>

    하지만, 하지만, 하지만! 위의 Demo 컴포넌트처럼 코드가 여기 저기 2~3줄밖에 없는데, 하나의 컴포넌트로 추출했는데 필요없다고 생각된다면 유일한 해결책은 CV 방식이겠죠? (물론 리스트 같은 것이라면 v-for 코드를 사용하면 되지만 여기서 소개하는 시나리오는 아닙니다)

    Vue3 재사용 가능한 구성 요소를 사용하는 방법급하신 건 알지만 아직 불안해하지 마세요. 컴포넌트 범위 내에서 재사용할 템플릿에 동그라미를 칠 수 있다면 Vue에게 알려주세요. 현재 이 기능을 지원하지 않는 것 같지만 여러 곳에서 사용해야 하기 때문에 이 코드에 동그라미를 쳤습니다. 괜찮습니다. 못 이루면 구현하겠습니다

    대략적인 구현 계획은 다음과 같습니다.

    <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 재사용 가능한 구성 요소를 사용하는 방법Vue3 재사용 가능한 구성 요소를 사용하는 방법

    그런데 이 솔루션을 어떻게 구현하나요? 결국 당신은 이미 하늘에 자랑했고, 그것을 이루지 못한다면 여전히 고난을 견뎌야 하는 것입니다. 좋습니다. antfu 사장이 실제로 createReusableTemplate이라는 것을 구현하고 VueUse에 넣었습니다. 문서를 클릭하면 세부정보를 볼 수 있습니다.

    Usage

    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>

    그런 다음 코드를 재사용하려는 위치에 DefineTemplate으로 래핑한 다음 ReuseTemplate을 사용하세요. 🎜
    const [DefineFoo, ReuseFoo] = createReusableTemplate<{ msg: string }>()
    🎜⚠️ DefineTemplate은 ReuseTemplate보다 먼저 사용해야 합니다🎜
    🎜createReusableTemplate은 Tuple을 반환합니다. 즉, 구성 요소 쌍을 정의하고 재사용한 다음 다음을 통해 위의 예에서는 단일 파일에서 여러 코드를 재사용할 수 있습니다. 🎜🎜또한 객체 분해를 통해 실제로 정의를 반환하고 재사용할 수 있습니다. (놀랍네요. 이 기사에서는 더 이상 설명하지 않겠습니다. 관심이 있으시면 다음에 공유하겠습니다.) 사용법은 위와 동일합니다. 예 는 다음과 같습니다🎜
    type DefineComponent<PropsOrPropOptions = {}, RawBindings = {}, ...>
    🎜🎜🎜입니다 정말 놀랍습니다, 어떻게 구현하나요🎜 🎜위에서 사용법을 소개했습니다. 누구도 이해하지 못할 거라 생각합니다. 실제로 시작하는 데 드는 비용은 0이므로 구현 방법을 살펴보겠습니다. 🎜🎜스크립트 설정을 사용하는 것 외에도 Vue3는 defineComponent를 통해 구성 요소를 정의할 수도 있다는 것을 알고 있습니다🎜
    type ReuseTemplateComponent<
      Bindings extends object,
      Slots extends Record<string, Slot | undefined>,
      /** Bindings使之有类型提示 */
    > = DefineComponent<Bindings> & {
     /** 插槽相关 */
      new(): { $slots: Slots }
    }
    🎜그럼 템플릿이 어떻게 정의되는지 살펴보겠습니다🎜
    type DefineTemplateComponent<
     Bindings extends object,
     Slots extends Record<string, Slot | undefined>,
     Props = {},
    > = DefineComponent<Props> & {
      new(): { $slots: { default(_: Bindings & { $slots: Slots }): any } }
    }
    🎜익숙해 보이죠? v-슬롯?, 에, 젠장, 이거 슬롯 아냐! 또한 템플릿 코드는 기본 슬롯에 배치되는 것 같습니다. 🎜🎜자, Define 기능을 어떻게 구현하는지 살펴보겠습니다. 🎜🎜Implementing Define🎜🎜템플릿이 기본 슬롯에 정의되어 있다고 말한 다음 나중에 템플릿에서 정의를 사용할 때 슬롯을 가져옵니다. 그냥 렌더링하는 게 낫지 않을까요? , 코드는 다음과 같습니다🎜
      const define = defineComponent({
        props {
          name: String
        }
      })
      const reuse = defineComponent({
        props {
          name: String
        }
      })
    🎜그렇습니다. Define의 경우 핵심 코드는 이 두세 줄입니다.🎜🎜implement Reuse🎜🎜위에서 렌더링 함수를 얻은 다음 Reuse를 사용하면 get v-slot, attrs 등을 렌더링에 전달하는 것이 좋지 않을까요? 🎜🎜마찬가지로 템플릿에서 Reuse를 사용하는 경우 설정에 들어간 다음 모든 매개변수를 렌더링에 전달하고 렌더링 결과를 반환합니다.🎜
    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 })
          }
        },
      })
    🎜위 attrs는 Reuse에 업로드한 props입니다🎜rrreee🎜그리고 왜 그런가요? $slots를 전달해야 하나요? 🎜🎜실제로 위의 예가 있습니다. 템플릿에는 동적 구성 요소인 <comcomponent :is="$slots.default"></comcomponent>🎜를 통해 슬롯의 실제 콘텐츠를 가져올 수도 있습니다. rrreee 🎜🎜🎜🎜물론 기본 슬롯뿐만 아니라 다른 이름의 슬롯도 가능합니다🎜rrreee🎜🎜🎜🎜꽃놀이 방법은 여러분의 몫~🎜🎜개발 경험 향상을 위한 지원 입력🎜🎜 템플릿을 정의했지만 템플릿이 어떤 매개변수를 받고 어떤 매개변수가 전달되는지 알려주셔야 합니다. 유형 프롬프트가 없으면 개발 경험이 매우 나빠질 것입니다. 하지만 걱정하지 마세요. 사장님이 이미 고려하셨습니다. 이것들. 🎜

    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으로 문의하시기 바랍니다. 삭제