【Vue】 组件内模板复用技巧:createReusableTemplate
在 Vue 组件开发中,我们经常遇到部分模板内容需要在同一组件内多次复用的场景。传统的解决方案如提取子组件、v-for 循环或直接复制粘贴,在某些简单场景下可能显得过于繁琐或冗余。
本文介绍一种利用 Vue 3 组合式 API 和渲染函数特性实现的“局部模板复用”技巧,类似于模板引擎中的“宏(Macro)”概念。
核心思路
该方案的核心在于利用 闭包 和 渲染函数:
- DefineTemplate:一个“插槽捕获器”。它不直接渲染内容,而是通过
setup函数获取默认插槽(slots.default)的渲染函数,并将其存储在一个局部变量中。 - UseTemplate:一个“渲染器”。它是一个简单的函数式组件,负责调用并渲染上述存储的渲染函数。
基础实现
以下是该模式的最简实现。
1. 定义工具组件
我们通过一个局部变量 render 来在两个组件之间共享渲染逻辑。
<script lang="ts" setup>
import { type Slot } from 'vue'
// 用于存储插槽渲染函数的变量
let render: Slot | undefined
// 1. 定义模板的组件
const DefineTemplate = {
setup(_: unknown, { slots }: { slots: { default: Slot } }) {
// 将插槽函数赋值给外部变量,注意:这里返回一个渲染函数
return () => {
render = slots.default
}
}
}
// 2. 使用模板的组件
const UseTemplate = () => {
// 调用存储的渲染函数
return render ? render() : null
}
</script>
2. 在模板中使用
<template>
<div>
<!-- 定义区:这部分内容不会直接渲染,而是被“捕获” -->
<DefineTemplate>
<div class="reused-box">
<span>这是复用的内容</span>
</div>
</DefineTemplate>
<!-- 使用区 -->
<header>
<h1>Page Header</h1>
<UseTemplate />
</header>
<main>
<p>Main Content</p>
<UseTemplate />
</main>
</div>
</template>
进阶封装:支持参数传递
为了让复用模板更灵活,我们需要支持通过 作用域插槽(Scoped Slots)传递数据。我们可以将其封装为一个通用的工具函数 createReusableTemplate。
优化后的工具函数
import { type Slot, type VNode } from 'vue'
/**
* 创建一对可复用的模板组件
* @returns [DefineTemplate, UseTemplate]
*/
export function createReusableTemplate<T extends object = any>() {
// 使用闭包保存渲染函数,确保每个实例独立
let render: Slot | undefined
// 定义组件:接收插槽并保存
const DefineTemplate = {
setup(_: unknown, { slots }: { slots: { default: Slot } }) {
return () => {
render = slots.default
}
}
}
// 使用组件:接收 props 并传递给渲染函数
const UseTemplate = (props: T): VNode[] | null => {
return render ? render(props) : null
}
return [DefineTemplate, UseTemplate] as const
}
使用示例
通过 v-slot 接收参数,实现动态内容渲染。
<template>
<div class="layout">
<!-- 定义模板:接收 title 参数 -->
<DefineTemplate v-slot="{ title }">
<div class="card">
<h3>当前区域:{{ title }}</h3>
<p>这是通用的卡片样式</p>
</div>
</DefineTemplate>
<div class="section">
<UseTemplate title="头部区域" />
</div>
<div class="section">
<UseTemplate title="侧边栏" />
</div>
</div>
</template>
<script lang="ts" setup>
// 假设上面的函数保存在 utils 中
// import { createReusableTemplate } from '@/utils'
// 实例化一对模板组件
const [DefineTemplate, UseTemplate] = createReusableTemplate<{ title: string }>()
</script>
最佳实践
虽然手写这个功能很有趣,但在生产环境中,建议直接使用 VueUse 库提供的 createReusableTemplate。它处理了更多的边界情况(如 Fragment、多个根节点、TS 类型推断等),更加健壮。
安装 VueUse:
npm i @vueuse/core
使用方式:
<script lang="ts" setup>
import { createReusableTemplate } from '@vueuse/core'
const [DefineTemplate, UseTemplate] = createReusableTemplate<{ msg: string }>()
</script>
<template>
<DefineTemplate v-slot="{ msg }">
<div>Message: {{ msg }}</div>
</DefineTemplate>
<UseTemplate msg="Hello" />
<UseTemplate msg="World" />
</template>
总结
这种模式利用了 Vue 的底层能力,提供了一种轻量级的组件内代码复用方式:
- 隔离性:模板仅在当前组件内有效,不污染全局注册。
- 简洁性:避免了为了一小段 HTML 专门创建一个
.vue文件的麻烦。 - 灵活性:结合作用域插槽,可以像函数一样传递参数。
推荐在组件内部存在重复结构,且该结构尚未复杂到需要拆分为独立组件时使用。
【Vue】 组件内模板复用技巧:createReusableTemplate
https://blog.jiang.in/archives/019b68d2-c226-76a9-bbbc-2a1d146ffaa8