在写 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];
};
关键解析
- 模板逻辑捕获
在Define
组件中,把插槽逻辑保存到render.value
,供Reuse
后续调用。 - 动态属性转换
自动把data-id
这种属性名转成dataId
,写起来省心多了。 - 动态渲染逻辑
Reuse
组件直接拿render.value
调用已定义的模板,动态生成内容。 - 类型安全
通过 TypeScript,给模板绑定的数据和插槽定义清晰的类型,编译时就能发现错误。
总结:
说了这么多,你是不是觉得模板复用一下子变简单了?useTemplate
帮你把模板逻辑和数据解耦,插槽不再乱,动态渲染更清爽,还省去一堆重复代码。
这工具特别适合动态表单、卡片列表、后台管理系统这些场景。如果你也常被这些问题困扰,不妨试试 useTemplate
,开发效率和代码质量都会大大提升!