【Vue】VueUse 中 createReusableTemplate 的妙用
在 Vue 3 开发中,虽然推荐使用模板语法,但在封装高阶组件或使用特定 UI 库(如 Naive UI、Element Plus、Ant Design Vue)的表格组件时,我们往往需要编写 Render 函数(h 函数)或使用 JSX/TSX 来处理复杂的自定义列渲染。
对于不熟悉渲染函数 API 的开发者来说,直接手写嵌套的 h 函数不仅开发效率低,代码的可读性和维护性也极差。本文将介绍如何利用 VueUse 的 createReusableTemplate 工具,优雅地解决这一痛点。
痛点分析:嵌套地狱
以 Naive UI 的数据表格组件 NDataTable 为例,假设我们需要在“操作”列中渲染两个带有二次确认气泡框(Popover)的按钮:“编辑”和“删除”。
如果使用原生的 h 函数编写,代码结构如下:
// ❌ 传统的 h 函数写法:嵌套层级深,阅读困难
import { h } from 'vue'
import { NButton, NDataTable, NPopover, NSpace } from 'naive-ui'
const columns = [
{ title: '名字', key: 'name' },
{
title: '操作',
key: 'action',
render: (row) => {
// 创建一个 NSpace 包裹两个按钮
return h(NSpace, {}, [
// 第一个 Popover:编辑
h(
NPopover,
{ trigger: 'hover' },
{
default: () => '点击编辑该行数据',
trigger: () => h(NButton, { size: 'small', type: 'primary' }, { default: () => '编辑' })
}
),
// 第二个 Popover:删除
h(
NPopover,
{ trigger: 'hover' },
{
default: () => '点击删除该行数据',
trigger: () => h(NButton, { size: 'small', type: 'error' }, { default: () => '删除' })
}
)
])
}
}
]
可以看到,即使是这样一个简单的需求,代码的嵌套层级也已经非常深,且难以直观地看出 DOM 结构。随着业务逻辑的增加(如添加点击事件、动态显隐等),这段代码将变得难以维护。
解决方案:createReusableTemplate
createReusableTemplate 是 VueUse 提供的一个实用工具。它的核心思想是:允许你在 <template> 块中使用熟悉的 HTML 语法定义一段 UI 结构,然后将其转换为一个可以在 JS/TS 中引用的组件。
1. 创建模板容器
首先,我们通过 createReusableTemplate 创建一对组件:Define(用于定义内容)和 Use(用于渲染内容)。
<script setup lang="ts">
import { createReusableTemplate } from '@vueuse/core'
// 定义泛型 { row: any } 以获得类型提示
const [DefineActionTemplate, UseActionTemplate] = createReusableTemplate<{ row: any }>()
</script>
2. 在 Template 中编写 UI
接下来,我们利用 DefineActionTemplate 组件,使用标准的 Vue 模板语法来编写操作列的结构。这里可以毫无障碍地使用 v-if、v-for、@click 等指令。
<template>
<div>
<!-- 定义可复用模板:通过 v-slot 接收 row 数据 -->
<DefineActionTemplate v-slot="{ row }">
<NSpace>
<!-- 编辑按钮 -->
<NPopover trigger="hover">
<template #trigger>
<NButton size="small" type="primary" @click="handleEdit(row)">编辑</NButton>
</template>
<span>点击编辑:{{ row.name }}</span>
</NPopover>
<!-- 删除按钮 -->
<NPopover trigger="hover">
<template #trigger>
<NButton size="small" type="error" @click="handleDelete(row)">删除</NButton>
</template>
<span>确认删除该行吗?</span>
</NPopover>
</NSpace>
</DefineActionTemplate>
<!-- 表格组件:像往常一样使用 -->
<NDataTable :columns="columns" :data="data" />
</div>
</template>
3. 在 Render 函数中引用
最后,回到 columns 的定义。现在我们不需要手写复杂的 DOM 结构了,只需要通过 h 函数渲染我们在模板中定义的 UseActionTemplate 组件,并将 row 数据传递给它即可。
const columns = [
{ title: '名字', key: 'name' },
{
title: '操作',
key: 'action',
// ✅ 优化后的写法:逻辑清晰,代码简洁
render: (row) => h(UseActionTemplate, { row })
}
]
方案优势
- 降低心智负担:开发者可以继续使用熟悉的模板语法编写复杂的 UI 结构,无需在脑中将 HTML 翻译成
h函数或 JSX。 - 提升可维护性:视图结构(Template)与数据配置(JS)分离,结构更加清晰,修改样式或添加交互变得非常简单。
- 类型安全:通过泛型支持,在模板中使用
row数据时可以获得完整的类型提示。 - 无缝集成:不需要引入构建步骤或配置 JSX/TSX 环境,纯 Vue 运行时即可支持。
适用场景
- 使用 Naive UI、Ant Design Vue 等组件库的
NDataTable/Table组件,需要自定义复杂列渲染时。 - 需要在组件内部多次复用同一段复杂的 DOM 结构,但又不想将其拆分为独立的
.vue文件时。 - 需要将模板结构传递给第三方库的回调函数时。
💡 小贴士:虽然 TSX 也是解决此类问题的强力工具,但
createReusableTemplate提供了一种更“Vue Native”的轻量级替代方案,特别适合那些不想在项目中引入 TSX 依赖的团队。