【Vue】 组件内模板复用技巧:createReusableTemplate

在 Vue 组件开发中,我们经常遇到部分模板内容需要在同一组件内多次复用的场景。传统的解决方案如提取子组件、v-for 循环或直接复制粘贴,在某些简单场景下可能显得过于繁琐或冗余。

本文介绍一种利用 Vue 3 组合式 API 和渲染函数特性实现的“局部模板复用”技巧,类似于模板引擎中的“宏(Macro)”概念。

核心思路

该方案的核心在于利用 闭包渲染函数

  1. DefineTemplate:一个“插槽捕获器”。它不直接渲染内容,而是通过 setup 函数获取默认插槽(slots.default)的渲染函数,并将其存储在一个局部变量中。
  2. 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
作者
Jiang
发布于
2025年12月29日
更新于
2025年12月29日
许可协议