Lzh on GitHub

createReusableTemplate 函数允许您在单个 Vue 组件内部定义和重用模板片段,而无需将其提取为单独的子组件。

动机

在 Vue 组件中,经常需要 重复使用某些模板部分。例如:

<template>
  <dialog v-if="showInDialog">
    <!-- something complex -->
  </dialog>
  <div v-else>
    <!-- something complex -->
  </div>
</template>

我们希望尽可能地重用代码。通常,我们可能会将这些重复的部分提取到单独的组件中。但是,在独立的组件中,您会 失去访问局部绑定(即父组件数据和方法)的能力。为它们定义 propsemits 有时会很繁琐。

因此,createReusableTemplate 函数旨在提供一种 在组件作用域内定义和重用模板 的方法。

用法

在上面的示例中,我们可以将其重构为:

<script setup>
import { createReusableTemplate } from '@vueuse/core'

const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>

<template>
  <DefineTemplate>
    <!-- something complex -->
  </DefineTemplate>

  <dialog v-if="showInDialog">
    <ReuseTemplate />
  </dialog>

  <div v-else>
    <ReuseTemplate />
  </div>
</template>
  • <DefineTemplate> 将注册模板但 不渲染任何内容
  • <ReuseTemplate> 将渲染由 <DefineTemplate> 提供的模板。
  • <DefineTemplate> 必须在 <ReuseTemplate> 之前使用。
建议尽可能提取为单独的组件。滥用此函数可能会导致您的代码库出现不良实践。

Options API 用法

当与 Options API 一起使用时,您需要 在组件 setup 之外定义 createReusableTemplate,并将其传递给 components 选项,以便在模板中使用它们。

<script>
import { createReusableTemplate } from '@vueuse/core'
import { defineComponent } from 'vue'

const [DefineTemplate, ReuseTemplate] = createReusableTemplate()

export default defineComponent({
  components: {
    DefineTemplate,
    ReuseTemplate,
  },
  setup() {
    // ...
    const data = 'Hello from parent'; // 示例数据
    return { data };
  },
})
</script>

<template>
  <DefineTemplate v-slot="{ data, msg, anything }">
    <div>{{ data }} passed from usage</div>
  </DefineTemplate>

  <ReuseTemplate :data="data" msg="The first usage" />
</template>

传递数据

您还可以使用 slots 将数据传递到模板:

  • 使用 v-slot="..." 来访问 <DefineTemplate> 上的数据。
  • 直接在 <ReuseTemplate> 上绑定数据以将其传递给模板。
<script setup>
import { createReusableTemplate } from '@vueuse/core'

const [DefineTemplate, ReuseTemplate] = createReusableTemplate()

const data = 'Hello';
const anotherData = 'World';
const something = 'VueUse';
</script>

<template>
  <DefineTemplate v-slot="{ data, msg, anything }">
    <div>{{ data }} passed from usage, message: {{ msg }}</div>
  </DefineTemplate>

  <ReuseTemplate :data="data" msg="The first usage" />
  <ReuseTemplate :data="anotherData" msg="The second usage" />
  <ReuseTemplate v-bind="{ data: something, msg: 'The third' }" />
</template>

TypeScript 支持

createReusableTemplate 接受一个 泛型类型,用于为传递给模板的数据提供类型支持:

<script setup lang="ts">
import { createReusableTemplate } from '@vueuse/core'

// 附带一对 `DefineTemplate` 和 `ReuseTemplate`
const [DefineFoo, ReuseFoo] = createReusableTemplate<{ msg: string }>()

// 您可以创建多个可重用模板
const [DefineBar, ReuseBar] = createReusableTemplate<{ items: string[] }>()
</script>

<template>
  <DefineFoo v-slot="{ msg }">
    <div>Hello {{ msg.toUpperCase() }}</div>
  </DefineFoo>
  <ReuseFoo msg="World" />

  <ReuseFoo :msg="1" />
</template>

或者,如果您不喜欢数组解构,以下用法也是合法的:

<script setup lang="ts">
  import { createReusableTemplate } from '@vueuse/core'

  const { define: DefineFoo, reuse: ReuseFoo } = createReusableTemplate<{
    msg: string
  }>()
</script>

<template>
  <DefineFoo v-slot="{ msg }">
    <div>Hello {{ msg.toUpperCase() }}</div>
  </DefineFoo>

  <ReuseFoo msg="World" />
</template>
<script setup lang="ts">
import { createReusableTemplate } from '@vueuse/core'

const TemplateFoo = createReusableTemplate<{ msg: string }>()
</script>

<template>
  <TemplateFoo.define v-slot="{ msg }">
    <div>Hello {{ msg.toUpperCase() }}</div>
  </TemplateFoo.define>

  <TemplateFoo.reuse msg="World" />
</template>
不支持在没有 v-bind 的情况下传递布尔值 props。有关更多详细信息,请参阅 注意事项 部分。

Props 和 Attributes

默认情况下,传递给 <ReuseTemplate> 的所有 propsattributes 都将传递给模板。如果您不希望某些 props 传递到 DOM,您需要定义运行时 props

import { createReusableTemplate } from '@vueuse/core'

const [DefineTemplate, ReuseTemplate] = createReusableTemplate({
  props: {
    msg: String,
    enable: Boolean,
  },
})

如果您不想向模板传递任何 props,您可以传递 inheritAttrs 选项:

import { createReusableTemplate } from '@vueuse/core'

const [DefineTemplate, ReuseTemplate] = createReusableTemplate({
  inheritAttrs: false,
})

传递 Slots

也可以从 <ReuseTemplate> 中传回 slots。您可以通过 $slots 访问 <DefineTemplate> 上的 slots

<script setup>
import { createReusableTemplate } from '@vueuse/core'

const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>

<template>
  <DefineTemplate v-slot="{ $slots, otherProp }">
    <div some-layout>
      <component :is="$slots.default" />
    </div>
  </DefineTemplate>

  <ReuseTemplate>
    <div>Some content</div>
  </ReuseTemplate>

  <ReuseTemplate>
    <div>Another content</div>
  </ReuseTemplate>
</template>

注意事项

布尔值 Props

与 Vue 的行为不同,定义为布尔值且未通过 v-bind 传递或缺失的 props 将分别解析为空字符串或 undefined

<script setup lang="ts">
  import { createReusableTemplate } from '@vueuse/core'

  const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{
    value?: boolean
  }>()
</script>

<template>
  <DefineTemplate v-slot="{ value }">
    {{ typeof value }}: {{ value }}
  </DefineTemplate>

  <ReuseTemplate :value="true" />
  <!-- boolean: true -->

  <ReuseTemplate :value="false" />
  <!-- boolean: false -->

  <ReuseTemplate value />
  <!-- string: -->

  <ReuseTemplate />
  <!-- undefined: -->
</template>

参考

此函数是从 vue-reuse-template 迁移而来。

有关重用模板的现有 Vue 讨论/问题:

替代方法:

类型声明

type ObjectLiteralWithPotentialObjectLiterals = Record<
  string,
  Record<string, any> | undefined
>
type GenerateSlotsFromSlotMap<
  T extends ObjectLiteralWithPotentialObjectLiterals,
> = {
  [K in keyof T]: Slot<T[K]>
}
export type DefineTemplateComponent<
  Bindings extends Record<string, any>,
  MapSlotNameToSlotProps extends ObjectLiteralWithPotentialObjectLiterals,
> = DefineComponent & {
  new (): {
    $slots: {
      default: (
        _: Bindings & {
          $slots: GenerateSlotsFromSlotMap<MapSlotNameToSlotProps>
        },
      ) => any
    }
  }
}
export type ReuseTemplateComponent<
  Bindings extends Record<string, any>,
  MapSlotNameToSlotProps extends ObjectLiteralWithPotentialObjectLiterals,
> = DefineComponent<Bindings> & {
  new (): {
    $slots: GenerateSlotsFromSlotMap<MapSlotNameToSlotProps>
  }
}
export type ReusableTemplatePair<
  Bindings extends Record<string, any>,
  MapSlotNameToSlotProps extends ObjectLiteralWithPotentialObjectLiterals,
> = [
  DefineTemplateComponent<Bindings, MapSlotNameToSlotProps>,
  ReuseTemplateComponent<Bindings, MapSlotNameToSlotProps>,
] & {
  define: DefineTemplateComponent<Bindings, MapSlotNameToSlotProps>
  reuse: ReuseTemplateComponent<Bindings, MapSlotNameToSlotProps>
}
export interface CreateReusableTemplateOptions<
  Props extends Record<string, any>,
> {
  /**
   * Inherit attrs from reuse component.
   *
   * @default true
   */
  inheritAttrs?: boolean
  /**
   * Props definition for reuse component.
   */
  props?: ComponentObjectPropsOptions<Props>
}
/**
 * This function creates `define` and `reuse` components in pair,
 * It also allow to pass a generic to bind with type.
 *
 * @see https://vueuse.org/createReusableTemplate
 */
export declare function createReusableTemplate<
  Bindings extends Record<string, any>,
  MapSlotNameToSlotProps extends
    ObjectLiteralWithPotentialObjectLiterals = Record<"default", undefined>,
>(
  options?: CreateReusableTemplateOptions<Bindings>,
): ReusableTemplatePair<Bindings, MapSlotNameToSlotProps>