🤔 为什么需要"模板函数"
在 Vue 2 Options API 时代,复用一段 纯 UI 片段 只有两条路:
-
💼 抽成组件 → 为了三五行模板写
.vue
、props、emits,重; -
📋 直接复制粘贴 → 维护地狱,一改全改。
Vue 3 带来了 组合式 API ,让逻辑可以"组合",但 <template>
依旧只能"整块"复用。
vue-reuse-template(已并入 VueUse)把"组合"思想延伸到模板层,让你:
-
🎯 把模板当函数定义(define)
-
🔁 在原地多次调用(reuse)
-
🚫 不创建额外组件实例 、不破坏作用域 、不传 props
⚙️ 核心概念:一对"模板 Hook"
typescript
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
| 名称 | 作用 | 是否渲染 |
| --- | --- | --- |
| DefineTemplate | 📝 声明模板片段 | ❌(占位) |
| ReuseTemplate | ▶️ 调用模板片段 | ✅(真正渲染) |
📦 安装与引入
bash
# 推荐直接装 VueUse
npm i @vueuse/core
vue
<script setup>
import { createReusableTemplate } from '@vueuse/core'
</script>
🚀 最小可运行示例
vue
<script setup>
import { createReusableTemplate } from '@vueuse/core'
// 1. 创建一对模板 Hook
const [DefineCard, ReuseCard] = createReusableTemplate()
</script>
<template>
<!-- 2. 定义:不会渲染,只是注册 -->
<DefineCard v-slot="{ title, content }">
<div class="card">
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
</DefineCard>
<!-- 3. 复用:真正渲染 -->
<ReuseCard title="文章 1" content="今天学了 vue-reuse-template" />
<ReuseCard title="文章 2" content="模板也能当函数用" />
</template>
<style scoped>
.card { border: 1px solid #ccc; padding: 12px; margin: 8px 0; }
</style>
🎯** 效果**:两张卡片,零组件文件,零 props。
🔧 进阶技巧
解构命名 & 多次定义
typescript
const { define: DefineChip, reuse: ReuseChip } = createReusableTemplate()
const [DefineBadge, ReuseBadge] = createReusableTemplate()
在循环里复用
vue
<ReuseCard
v-for="post in posts"
:key="post.id"
:title="post.title"
:content="post.body"
/>
与组件作用域无缝衔接
模板内部可直接使用当前组件的变量、注入、Router、Store:
vue
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
</script>
<DefineCard v-slot="{ title }">
<h3>{{ title }}</h3>
<p>当前用户:{{ userStore.name }}</p>
</DefineCard>
具名插槽 + 默认插槽
vue
<DefineComplex v-slot="{ title }">
<header>{{ title }}</header>
<slot name="extra" />
</DefineComplex>
<ReuseComplex title="标题">
<template #extra><button>更多</button></template>
</ReuseComplex>
🎯 TypeScript 泛型推导(可选)
typescript
const [Define, Reuse] = createReusableTemplate<{
title: string
done: boolean
}>()
// 如果写 :title="123" 会立即报错
📊 与"全局注册"插件的区别
| 方案 | 跨组件 | 是否需插件 | 是否运行时 | 插槽传参 |
| --- | --- | --- | --- | --- |
| createReusableTemplate | ❌(仅当前组件) | 否 | 否 | ✅ |
| unplugin-vue-reuse-template | ✅ | 是(编译) | 否 | ✅(实验) |
💡 80% 场景 单组件内复用 已够用;如需 任意组件 调用,再考虑插件。
💡 示例:低代码表单渲染器
vue
<script setup>
import { createReusableTemplate } from '@vueuse/core'
const [DefineField, ReuseField] = createReusableTemplate()
const schema = [
{ type: 'input', label: '姓名', key: 'name' },
{ type: 'select', label: '城市', key: 'city', options: ['北京', '上海'] }
]
const form = reactive({})
</script>
<template>
<!-- 定义:一行通用字段 -->
<DefineField v-slot="{ field }">
<label>{{ field.label }}
<input v-if="field.type==='input'" v-model="form[field.key]" />
<select v-else v-model="form[field.key]">
<option v-for="o in field.options" :key="o">{{ o }}</option>
</select>
</label>
</DefineField>
<!-- 渲染 -->
<ReuseField v-for="f in schema" :key="f.key" :field="f" />
<pre>{{ form }}</pre>
</template>
✨** 亮点**
-
🚫 没有为"字段"创建额外组件
-
🔗 仍能访问
form
响应式对象 -
📖 模板即函数,可读性高
⚡ 性能与原理
-
⏱️ 编译期 :
DefineTemplate
被编译成 渲染函数块并缓存 -
▶️ 运行时 :
ReuseTemplate
直接 调用该函数块,不生成新的组件实例 -
🔗 作用链 :通过 Vue 3 的 作用域插槽 保持变量访问
-
🧹 零内存泄漏:随组件卸载自动回收
⚠️ 常见坑与最佳实践
| 问题 | 解决方案 |
| --- | --- |
| 🚨 模板未定义 | 保证 Define*
在 Reuse*
之前渲染(同一组件内) |
| 🌐 跨组件复用 | 目前 做不到,请改用插件或抽正式组件 |
| 🪆 大量嵌套 | 模板函数也能"组合"------在 DefineA
内部再 ReuseB
|
| 🏷️ 命名冲突 | 统一前缀:DefineUserCard
、DefineProductCard
|
📋 何时用 & 何时不用
✅ 适合
-
🎯 同组件内 重复 UI 结构(卡片、按钮组、表单项)
-
🚫 逻辑简单,不值得为 props 写接口
-
🔗 需要 访问父组件变量(不想层层传递)
❌ 不适合
-
🌐 跨组件复用(目前做不到)
-
🧠 业务逻辑复杂(应抽独立组件)
-
⚡ 需要 keep-alive 、生命周期 、name 等组件特性
🎯 小结
vue-reuse-template
让 模板也能像函数一样被定义和调用,不写 props、不创组件、不打断作用域------是 Vue 3 组合式时代里,最轻量的"模板函数"方案。
🚀 快速上手(单组件)
vue
<script setup>
import { createReusableTemplate } from '@vueuse/core'
const [Def, Reuse] = createReusableTemplate()
</script>
<template>
<Def v-slot="{ txt }"><p>{{ txt }}</p></Def>
<Reuse txt="Hello" />
<Reuse txt="World" />
</template>
🌍 全局注册(任意 .vue
可用)
① 安装插件
bash
npm i -D unplugin-vue-reuse-template
② Vite 配置
typescript
// vite.config.js
import vueReuse from 'unplugin-vue-reuse-template/vite'
export default {
plugins: [vue(), vueReuse()]
}
③ 定义全局模板
vue
<!-- src/components/TemplateBank.vue -->
<template>
<template $btn>
<button class="btn"><slot /></button>
</template>
<template $card>
<div class="card"><slot name="title"/><slot/></div>
</template>
</template>
<style>
.btn{@apply px-4 py-2 bg-blue-500 text-white rounded}
.card{@apply border rounded p-4 shadow}
</style>
在 main.js
里确保被预加载
javascript
import '@/components/TemplateBank.vue'
④ 任意组件直接调用
vue
<template>
<template $use="$btn">提交</template>
<template $use="$card">
<template #title><h3>标题</h3></template>
<p>内容</p>
</template>
</template>
📊 小结
| 方式 | 范围 | 语法 | 插件 |
| --- | --- | --- | --- |
| createReusableTemplate
| 当前组件 | Def/Reuse
| 不需要 |
| unplugin-vue-reuse-template
| 全局 | <template $use>
| 需要