Vue3复用组件怎么使用

发布时间:2023-04-17 15:07:00 作者:iii
来源:亿速云 阅读:167

这篇文章主要讲解了“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>

Vue3复用组件怎么使用

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

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 !== '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>

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 分钟不到应该不是吹的,确实实现很简单,但功能又很好用,解决了无法在单文件复用代码的问题。

我们来小结一下:

加个彩蛋吧

实际上多次调用 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 || '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复用组件怎么使用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. Vue3父子组件间通信、组件间双向绑定的方法
  2. 如何在vue3中使用setup、 ref、reactive

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

vue3

上一篇:Python的if逻辑判断分支怎么使用

下一篇:Android library native调试代码遇到问题怎么解决

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》