跳到主要内容

createReusableTemplate

Category
导出大小
576 B
Last Changed
last month

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>

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> will register the template and renders nothing.
  • <ReuseTemplate> will render the template provided by <DefineTemplate>.
  • <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.

Options API

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 { createReusableTemplate } from '@vueuse/core'
import { defineComponent } from 'vue'

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

  • Use v-slot="..." to access the data on <DefineTemplate>
  • 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 Support

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>

WARNING

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

Props and Attributes

By default, all props and attributes passed to <ReuseTemplate> will be passed to the template. If you don't want certain props to be passed to the DOM, you need to define the runtime props

ts
import { createReusableTemplate } from '@vueuse/core'

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

If you don't want to pass any props to the template, you can pass the inheritAttrs option

ts
import { createReusableTemplate } from '@vueuse/core'

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

Passing Slots

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>

Caveats

Boolean props

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

This function is migrated from vue-reuse-template.

Existing Vue discussions/issues about reusing template

Alternative Approaches

Type Declarations

Show Type Declarations
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<
  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.cn/createReusableTemplate
 */
export declare function createReusableTemplate<
  Bindings extends Record<string, any>,
  MapSlotNameToSlotProps extends
    ObjectLiteralWithPotentialObjectLiterals = Record<"default", undefined>,
>(
  options?: CreateReusableTemplateOptions<Bindings>,
): ReusableTemplatePair<Bindings, MapSlotNameToSlotProps>

Source

SourceDocs

Contributors

Anthony Fu
Anthony Fu
Guspan Tanadi
shelton louis
Carlos Rodrigues
Issayah
Kasper Seweryn
小的的 DeDe
Maik Kowol
coderwei
Jean-Baptiste AUBRÉE
Andrej Hýll

更新日志

v12.6.0 于 2025/2/14
18031 - 特性: 显式 props (#4535)
v12.0.0-beta.1 于 2024/11/21
0a9ed - 特性!: 移除 Vue 2 支持,优化 bundles 并清理 (#4349)
v10.8.0 于 2024/2/20
75168 - 修复: 改进类型 (#3641)
a086e - 修复: 更严格的类型
v10.3.0 于 2023/7/30
a32ae - 特性: 继承 attrs (#3226)
d79e1 - 修复: 驼峰化 props (#3253)
v10.1.1 于 2023/5/1
b3323 - 修复: 改进 hmr 支持
v10.0.0-beta.5 于 2023/4/13
13169 - 特性(createTemplatePromise): 新函数 (#2957)
bd53c - 特性: 新函数 (#2961)

在 MIT 许可证下发布。