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:
<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:
<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.
<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:
使用
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
<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:
<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:
<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
的布尔属性。有关详细信息,请参阅 注意事项 部分。
¥Passing boolean props without v-bind
is not supported. See the Caveats section for more details.
参数和属性
¥Props and Attributes
默认情况下,传递给 <ReuseTemplate>
的所有属性和属性都将传递给模板。如果你不想将某些属性传递给 DOM,则需要定义运行时属性:
¥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:
import { createReusableTemplate } from '@vueuse/core'
const [DefineTemplate, ReuseTemplate] = createReusableTemplate({
props: {
msg: String,
enable: Boolean,
}
})
如果你不想将任何属性传递给模板,可以传递 inheritAttrs
选项:
¥If you don't want to pass any props to the template, you can pass the inheritAttrs
option:
import { createReusableTemplate } from '@vueuse/core'
const [DefineTemplate, ReuseTemplate] = createReusableTemplate({
inheritAttrs: false,
})
传递插槽
¥Passing Slots
也可以从 <ReuseTemplate>
传回插槽。你可以从 $slots
访问 <DefineTemplate>
上的插槽:
¥It's also possible to pass slots back from <ReuseTemplate>
. You can access the slots on <DefineTemplate>
from $slots
:
<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
与 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:
<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:
类型声明
显示类型声明
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>