VTable 高级列模板与展开行方案
概述
把表格继续往官方高级案例推进,重点落在自定义列模板、自定义表头、展开行三类能力上。它们看起来像三个不同示例,但核心其实是同一个:某一列不再只是简单地渲染 row[prop],而是需要一套定制化 DOM 结构。这三类能力天然和 slot、TSX、h 这些更灵活的渲染方式绑在一起。本章梳理高级列渲染的三条路线(slot、TSX/h、独立组件),说明 defaultSlot 与 headerSlot 的统一落点,指出展开行仍依赖特殊列与内容区,并提醒 TSX 里 v-model 必须拆成 modelValue + onInput。长期看,JSON 列配置比纯模板更适合复杂表格维护。
学习目标
- 理解自定义列模板、自定义表头、展开行都属于"高级列渲染"同一范畴
- 对比高级列渲染的三条路线:slot、TSX/JSX、h 渲染函数
- 掌握复杂列模板优先抽成独立组件的判断标准
- 用 defaultSlot 指向自定义组件或 TSX 渲染函数
- 用 headerSlot 承接自定义表头,复用列级扩展体系
- 理解展开行本质仍依赖特殊列与特殊内容区
- 认识 JSON 列配置在规模化维护上优于纯模板 slot
- 记住 TSX 中的 v-model 需拆成 modelValue + onInput / onUpdate
一、三类高级能力本质同一:列不再只显示字符串
自定义列模板、自定义表头、展开行看起来表现不同,但核心其实是同一个:某一列不再只是简单地渲染 row[prop],而是需要一套定制化 DOM 结构。也正因为这样,这三类能力都天然和 slot、TSX、h 绑在一起。
普通列 -> row[prop]
高级列 -> 自定义 DOM / 组件 / 插槽结构操作按钮列(Detail/Edit/Delete)、自定义表头(输入框+标题)、展开行(展开后渲染更多内容甚至嵌套表格),都是"高级列渲染"的具体形态。一旦走到这里,表格组件的灵活性就主要取决于你的列渲染方案。
二、高级列渲染的三条路线
高级列渲染常见有三条路线:直接写官方那种 slot 模板、用 TSX/JSX、用 h 函数。它们并不是互相替代的"谁更高级",而是适合的场景不同。slot 直观,适合简单结构;TSX/JSX 灵活,组件组合自由;h 更底层,适合程序化生成结构。
简单列模板 -> slot
需要函数式渲染 -> TSX / h不是所有高级列都值得上 TSX;结构简单时,slot 反而更清晰。真正要看的是:这列会不会继续复杂化、复用化。
三、列模板超复杂度时抽独立组件
如果一列的 TSX/JSX 很短,直接写在配置里还能接受。但如果 DOM 嵌套变多、交互逻辑变复杂、还需要接 row、事件回调、样式,那继续把它写在一个大箭头函数里,很快会让列配置变得难读。这时候更好的方案通常是抽成单独组件,让列配置只负责"引用这个组件"。
{
label: "Popover",
defaultSlot: PopoverCell
}组件抽离的目标不是"多文件",而是降低列配置的认知负担;列模板组件一旦独立,也更方便单测和后续复用。表格列渲染越复杂,越要避免把页面配置写成一大坨匿名函数。
四、defaultSlot 是高级列模板的主入口
自定义列模板最直接的实现,就是让某一列的 defaultSlot 指向自定义组件或 TSX 渲染函数。这里的本质仍然是前面已经铺好的那条线:在列配置里提供 defaultSlot,区别只是以前默认是操作列,现在可以变成更复杂的自定义内容列。
const customColumns: VTableColumnType[] = [
{ label: "Popover", defaultSlot: PopoverCell },
{ label: "Name", defaultSlot: (scope) => <span>{scope.row.name}</span> }
]defaultSlot 可以接组件,也可以接渲染函数;组件方案通常更适合复杂 UI,函数方案更适合轻量定制。
五、headerSlot 是自定义表头的天然落点
自定义表头和展开行本质和前面是同一套思路。对于列级扩展来说,单元格定制用 defaultSlot,表头定制用 headerSlot。只要 VTable 已经支持 headerSlot 和 defaultSlot,那么自定义表头其实已经有落点了。
{ label: "Name", headerSlot: NameHeader }自定义表头不是独立能力,而是列级渲染体系的另一半。如果你已经支持列级 headerSlot,就等于为复杂表头打开了入口;后面做"头部输入筛选"这类交互时,headerSlot 会非常有用。
六、展开行仍回到特殊列与内容区
展开行是一个比操作列和表头更进阶的案例,复杂之处在于它会多出一层内容区,展开后甚至还能继续嵌套表格。但从组件设计角度看,它最终仍然要解决两个问题:这一列的展开触发器怎么渲染,展开内容怎么组织。也就是说,展开行虽然更像"表格行为",但它的渲染层依旧会落到列配置和插槽上。
展开行 -> 特殊列 + 特殊内容区展开行比普通列模板复杂,因为它同时涉及行结构和内容区扩展;这类能力非常适合作为"高级示例"存在,不一定要第一时间完全抽象进基础组件。
七、复杂场景 JSON 配置比模板更可维护
只靠模板 slot 写一两个高级列没问题,但一旦列数多、场景多、嵌套多,JSON 配置方案会明显更可控。因为只要列结构数据化了,你就更容易继续做配置驱动、权限控制、列显示隐藏、复杂列复用。
const columns = [
{ label: "Name", defaultSlot: NameCell },
{ label: "Popover", defaultSlot: PopoverCell },
{ label: "Operations", defaultSlot: OperationCell }
]课程里示范了 slot、TSX、组件化三条路线,但长期看更推荐配置化收口。复杂表格的本质不是"模板写法炫不炫",而是能不能长期维护;JSON 方案会让 VTable 更像真正的组件库能力。
八、TSX 中的 v-model 必须拆开
模板里写 v-model 很自然,但在 TSX/JSX 里不能直接照抄这套写法,通常要拆成 modelValue 和 onUpdate:modelValue,或者某些组件直接使用 value 和 onInput。这也是很多只写模板的小伙伴一切到 TSX 就会头疼的原因。
<ElInput
modelValue={keyword}
onInput={(value) => { keyword = value }}
/>模板:v-model="keyword"
TSX:modelValue + onInput / onUpdate:modelValueTSX 不是模板语法的直接复制版,一旦决定用 TSX 就要接受它更函数式的写法;如果团队主要写模板,复杂度高的 TSX 更值得继续抽成单独组件。
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 复杂列模板越写越像一坨页面代码 | 一直把长段 TSX 写在列配置里 | 超过一定复杂度后立刻抽独立组件 |
| 自定义表头总想再设计一套新 API | 忽略了已有 headerSlot 就是天然入口 | 直接复用列级 headerSlot 扩展 |
| 展开行不好抽象 | 把它当成完全独立于列的能力 | 先理解它本质上仍依赖特殊列和特殊内容区 |
| TSX 中的 v-model 写法直接报错 | 把模板语法照搬到了 JSX | 改成 modelValue + onInput / onUpdate |
| 觉得 slot 比 JSON 直观所以不想配置化 | 只看到短期书写体验 | 一旦列数和场景变多,JSON 配置维护性更好 |
| 操作列、表头、展开行各写一套方案 | 没统一成高级列渲染问题来理解 | 回到 defaultSlot / headerSlot / children 统一模型 |