在写 Vue 项目时,我们经常会遇到这样的场景:组件模板逻辑重复、动态渲染复杂、插槽难管理……是不是每次都想骂一句「为啥不能简单点?」别急,这篇文章带你解决这些痛点,用一个函数 useTemplate,轻松搞定模板复用,效率提升不止一点点。


开发中的那些痛点

1. 模板重复写,烦!

比如我们要渲染一组卡片,模板结构都是类似的,但每个卡片内容不一样。按传统写法,可能要重复写好多模板代码。

<!-- 卡片 1 -->
<div class="card">
  <h2>卡片 1</h2>
  <p>内容 1</p>
</div>

<!-- 卡片 2 -->
<div class="card">
  <h2>卡片 2</h2>
  <p>内容 2</p>
</div>

看起来挺简单,但如果有 50 个卡片呢?不仅写起来累,后期改逻辑也麻烦。


2. 插槽逻辑太乱,管不住!

Vue 的插槽挺好用,但要复用一段复杂的插槽模板,每次都得手动塞内容,比如这样:

<template>
  <div>
    <slot name="title"></slot>
    <slot name="content"></slot>
  </div>
</template>

用起来像这样:

<MyComponent>
  <template #title>标题 1</template>
  <template #content>内容 1</template>
</MyComponent>

这就导致逻辑分散到各个地方,而且维护起来巨麻烦,一改模板就得全局找一圈。


3. 动态渲染太费劲

想根据后台传回来的数据动态生成页面,比如动态表单、动态卡片,每次都要用 v-if<component> 写一堆判断,逻辑不直观,组件重复初始化,效率还低。


4. 命名风格不一致,容易翻车

HTML 属性用短横线命名(data-id),JavaScript 又喜欢驼峰(dataId)。写多了,这种不一致真的让人抓狂。


5. 类型不安全,调试靠运气

数据绑定和插槽参数类型说不清楚,运行时才发现有错,调试起来头发掉一大把。


useTemplate 一次解决所有问题

痛点够多了,别急,下面咱们就拿出解决方案:一个高效的工具函数 —— useTemplate。它能让你轻松复用模板,无需重复写代码,还能动态渲染不同的数据。

它怎么用?

useTemplate 返回两个组件:

  • Define:用来定义模板,捕获你的渲染逻辑。
  • Reuse:用来复用模板,直接传数据就能生成不同的内容。

直接看例子,一看就懂。


场景 1:动态卡片渲染

定义模板

<Define>
  <template #default="{ title, content }">
    <div class="card">
      <h2>{{ title }}</h2>
      <p>{{ content }}</p>
    </div>
  </template>
</Define>

复用模板

<Reuse v-bind="{ title: '卡片 1', content: '内容 1' }" />
<Reuse v-bind="{ title: '卡片 2', content: '内容 2' }" />

这样写,所有卡片共用同一个模板,逻辑只写一次,随便新增、修改内容都很方便。


场景 2:动态表单生成

定义模板

<Define>
  <template #default="{ label, value, type }">
    <div>
      <label>{{ label }}</label>
      <input :type="type" :value="value" />
    </div>
  </template>
</Define>

复用模板

<Reuse v-bind="{ label: '用户名', value: '', type: 'text' }" />
<Reuse v-bind="{ label: '密码', value: '', type: 'password' }" />

这种写法特别适合动态表单。要改表单样式,直接改模板;新增表单项,直接传数据,一秒搞定。


动态渲染加持:一键批量生成

从后台拿到一组数据,比如卡片信息:

const cards = [
  { id: 1, title: '卡片 A', content: '内容 A' },
  { id: 2, title: '卡片 B', content: '内容 B' },
];

直接用 v-for 渲染:

<Reuse v-for="card in cards" :key="" v-bind="card" />

简洁直观,动态数据动态渲染,再也不用写 v-if 判断了。


useTemplate 函数的实现

知道怎么用以后,我们来拆解一下 useTemplate 的内部实现。

核心代码

import { defineComponent, shallowRef } from 'vue';
import { camelCase } from 'lodash';

// 把属性从短横线命名改为驼峰命名
function keysToCamelKebabCase(obj: Record<string, any>) {
  const newObj: typeof obj = {};
  for (const key in obj) newObj[camelCase(key)] = obj[key];
  return newObj;
}

// 核心函数
export const useTemplate = <Bindings extends object>() => {
  const render = shallowRef();

  // 定义模板组件
  const Define = defineComponent({
    setup(_, { slots }) {
      return () => {
        render.value = slots.default; // 捕获插槽逻辑
      };
    },
  });

  // 复用模板组件
  const Reuse = defineComponent({
    setup(_, { attrs, slots }) {
      return () => {
        if (!render.value) throw new Error('模板未定义!');
        // 动态执行模板渲染逻辑
        const vnode = render.value({ ...keysToCamelKebabCase(attrs), $slots: slots });
        return vnode.length === 1 ? vnode[0] : vnode;
      };
    },
  });

  return [Define, Reuse];
};

关键解析

  1. 模板逻辑捕获
    Define 组件中,把插槽逻辑保存到 render.value,供 Reuse 后续调用。
  2. 动态属性转换
    自动把 data-id 这种属性名转成 dataId,写起来省心多了。
  3. 动态渲染逻辑
    Reuse 组件直接拿 render.value 调用已定义的模板,动态生成内容。
  4. 类型安全
    通过 TypeScript,给模板绑定的数据和插槽定义清晰的类型,编译时就能发现错误。

总结:

说了这么多,你是不是觉得模板复用一下子变简单了?useTemplate 帮你把模板逻辑和数据解耦,插槽不再乱,动态渲染更清爽,还省去一堆重复代码。

这工具特别适合动态表单、卡片列表、后台管理系统这些场景。如果你也常被这些问题困扰,不妨试试 useTemplate,开发效率和代码质量都会大大提升!