主题
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 { 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
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>