【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-ifv-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 })
  }
]

方案优势

  1. 降低心智负担:开发者可以继续使用熟悉的模板语法编写复杂的 UI 结构,无需在脑中将 HTML 翻译成 h 函数或 JSX。
  2. 提升可维护性:视图结构(Template)与数据配置(JS)分离,结构更加清晰,修改样式或添加交互变得非常简单。
  3. 类型安全:通过泛型支持,在模板中使用 row 数据时可以获得完整的类型提示。
  4. 无缝集成:不需要引入构建步骤或配置 JSX/TSX 环境,纯 Vue 运行时即可支持。

适用场景

  • 使用 Naive UI、Ant Design Vue 等组件库的 NDataTable / Table 组件,需要自定义复杂列渲染时。
  • 需要在组件内部多次复用同一段复杂的 DOM 结构,但又不想将其拆分为独立的 .vue 文件时。
  • 需要将模板结构传递给第三方库的回调函数时。

💡 小贴士:虽然 TSX 也是解决此类问题的强力工具,但 createReusableTemplate 提供了一种更“Vue Native”的轻量级替代方案,特别适合那些不想在项目中引入 TSX 依赖的团队。


【Vue】VueUse 中 createReusableTemplate 的妙用
https://blog.jiang.in/archives/019b68d6-0445-733f-95e0-ac9bab82c45e
作者
Jiang
发布于
2025年12月29日
更新于
2025年12月29日
许可协议