Skip to content

createReusableTemplate

在组件范围内定义和重用模板。

¥Define and reuse template inside the component scope.

动机

¥Motivation

需要重用模板的某些部分是很常见的。例如:

¥It's common to have the need to reuse some part of the template. For example:

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

我们希望尽可能地重用我们的代码。所以通常我们可能需要将这些重复的部分提取到一个组件中。但是,在单独的组件中,你将失去访问本地绑定的能力。为它们定义 props 和触发有时可能很乏味。

¥We'd like to reuse our code as much as possible. So normally we might need to extract those duplicated parts into a component. However, in a separated component you lose the ability to access the local bindings. Defining props and emits for them can be tedious sometimes.

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

¥So this function is made to provide a way for defining and reusing templates inside the component scope.

用法

¥Usage

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

¥In the previous example, we could refactor it to:

vue
<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> 将注册模板并且不渲染任何内容。

    ¥<DefineTemplate> will register the template and renders nothing.

  • <ReuseTemplate> 将渲染 <DefineTemplate> 提供的模板。

    ¥<ReuseTemplate> will render the template provided by <DefineTemplate>.

  • <DefineTemplate> 必须在 <ReuseTemplate> 之前使用。

    ¥<DefineTemplate> must be used before <ReuseTemplate>.

注意:建议尽可能将其作为单独的组件提取。滥用此函数可能会导致你的代码库出现不良做法。

¥Note: It's recommended to extract as separate components whenever possible. Abusing this function might lead to bad practices for your codebase.

选项 API

¥Options API

选项 API 一起使用时,你需要在组件设置之外定义 createReusableTemplate 并传递给 components 选项,以便在模板中使用它们。

¥When using with Options API, you will need to define createReusableTemplate outside of the component setup and pass to the components option in order to use them in the template.

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

const [DefineTemplate, ReuseTemplate] = createReusableTemplate()

export default defineComponent({
  components: {
    DefineTemplate,
    ReuseTemplate,
  },
  setup() {
    // ...
  },
})
</script>

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

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

传递数据

¥Passing Data

你还可以使用槽将数据传递到模板:

¥You can also pass data to the template using slots:

  • 使用 v-slot="..." 访问 <DefineTemplate> 上的数据

    ¥Use v-slot="..." to access the data on <DefineTemplate>

  • 直接绑定 <ReuseTemplate> 上的数据传递给模板

    ¥Directly bind the data on <ReuseTemplate> to pass them to the template

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

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

<template>
  <DefineTemplate v-slot="{ data, msg, anything }">
    <div>{{ data }} passed from usage</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 支持

¥TypeScript Support

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

¥createReusableTemplate accepts a generic type to provide type support for the data passed to the template:

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

// Comes with pair of `DefineTemplate` and `ReuseTemplate`
const [DefineFoo, ReuseFoo] = createReusableTemplate<{ msg: string }>()

// You can create multiple reusable templates
const [DefineBar, ReuseBar] = createReusableTemplate<{ items: string[] }>()
</script>

<template>
  <DefineFoo v-slot="{ msg }">
    <!-- `msg` is typed as `string` -->
    <div>Hello {{ msg.toUpperCase() }}</div>
  </DefineFoo>

  <ReuseFoo msg="World" />

  <!-- @ts-expect-error Type Error! -->
  <ReuseFoo :msg="1" />
</template>

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

¥Optionally, if you are not a fan of array destructuring, the following usages are also legal:

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

警告

Vue 2 不支持点表示法。

¥Dot notation is not supported in Vue 2.

警告

不支持传递没有 v-bind 的布尔属性。有关详细信息,请参阅 注意事项 部分。

¥Passing boolean props without v-bind is not supported. See the Caveats section for more details.

传递插槽

¥Passing Slots

也可以从 <ReuseTemplate> 传回插槽。你可以从 $slots 访问 <DefineTemplate> 上的插槽:

¥It's also possible to pass slots back from <ReuseTemplate>. You can access the slots on <DefineTemplate> from $slots:

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

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

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

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

警告

传递槽在 Vue 2 中不起作用。

¥Passing slots does not work in Vue 2.

注意事项

¥Caveats

布尔属性

¥Boolean props

与 Vue 的行为相反,定义为 boolean 且没有 v-bind 或不存在的情况下传递的 props 将分别解析为空字符串或 undefined

¥As opposed to Vue's behavior, props defined as boolean that were passed without v-bind or absent will be resolved into an empty string or undefined respectively:

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

参考

¥References

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

¥This function is migrated from vue-reuse-template.

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

¥Existing Vue discussions/issues about reusing template:

替代方法:

¥Alternative Approaches:

类型声明

显示类型声明
typescript
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 {
  /**
   * Inherit attrs from reuse component.
   *
   * @default true
   */
  inheritAttrs?: boolean
}
/**
 * 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,
): ReusableTemplatePair<Bindings, MapSlotNameToSlotProps>

源代码

源代码文档

变更日志

No recent changes

VueUse 中文网 - 粤ICP备13048890号