VFormItem 布局容器与多组件渲染
概述
单字段渲染跑通后,真实业务表单立刻会提出布局需求:一个字段里可能要并排放日期选择器加时间选择器,或者一组联动的选择控件。本章引入 VFormCol 作为布局层,把"字段级渲染"再拆出一层"列布局",并通过在 FormSchema 上加 children 描述选项型控件的子节点。最终形成 VForm / VFormItem / VFormCol 三层结构,让"一个逻辑字段"和"一行里的多个物理控件"可以清晰分离。
学习目标
- 理解为什么字段级渲染之外还需要布局层
- 掌握
VFormCol作为列布局容器的定位 - 用
schema上的children描述选项型控件的子节点 - 认识三栏/多控件的字段布局(如 DatePicker + Span + TimePicker)
- 理解
VForm/VFormItem/VFormCol三层拆分职责 - 区分"一个逻辑字段"和"一行多个物理控件"
- 掌握嵌套 VFormItem 渲染子结构的递归思路
- 认识布局变化与校验展示之间的耦合点
- 掌握响应式 span 与栅格断点的用法
一、字段级渲染之外为什么还需要布局层
VFormItem 解决的是"渲染哪个控件",但它不该同时承担"控件在格子里怎么排"。当某个字段需要一行里放多个控件(例如开始日期 + 至 + 结束日期),或者需要跨列布局时,如果把这些布局逻辑直接写进 VFormItem,它会迅速变得臃肿且难以复用。引入 VFormCol 这类布局容器,把"列/栅格"概念独立出来,是控制复杂度的必要一步。
二、VFormCol 是列布局容器
VFormCol 的职责很单纯:在一个表单行里占位并包裹一组控件,类似栅格系统里的列。它本身不关心具体渲染什么控件,只负责宽度占比(span)、顺序和嵌套。把布局交给 VFormCol,VFormItem 就能专注"字段是什么类型、绑什么值"。这种职责切分让布局能力和字段能力可以各自演进,不会互相拖累。
三、用 children 描述选项型控件的子节点
选项型控件(下拉、单选、多选)的特殊之处在于它们内部还有一层子节点:el-select 需要一堆 el-option,el-radio-group 需要一堆 el-radio。这些子节点不能靠 type 一个字段表达,必须在 FormSchema 上增加 children 数组,每项描述一个选项的 label 与 value。渲染时 VFormItem 识别到 children 存在,就遍历生成对应子节点。这是表单 Schema 比表格列配置多出来的一层结构复杂度。
{
field: "region",
label: "区域",
type: "select",
children: [
{ label: "华东", value: "east" },
{ label: "华南", value: "south" }
]
}四、一个字段里并排多个控件的布局
真实表单经常有"组合字段":开始日期、连接符、结束日期三者其实逻辑上是一个时间区间,但视觉上要并排。此时可以在 VFormItem 内部放多个 VFormCol,每个列里再渲染一个控件。span 属性控制每列占几份,连接符这类纯文本也可以作为一个无绑定控件占一列。这样既保留了"一个字段对应一个数据键"的简洁,又满足了视觉上的并排需求。
五、三层拆分:VForm / VFormItem / VFormCol
到这一步,表单渲染形成清晰的三层:VForm 是整表容器,承接 model 与 rules;VFormItem 是字段级渲染器,决定控件类型与数据绑定;VFormCol 是列布局容器,决定控件在行内如何排布。三层各管一摊,新增一种布局不会影响字段渲染逻辑,新增一种控件也不会打乱布局。这种分层和表格里 VTable / VTableColumn / 布局容器 的拆分思路一脉相承。
六、逻辑字段与物理控件的分离
要始终区分两件事:逻辑字段(对应 model 的一个 key,决定提交什么数据)和物理控件(屏幕上实际渲染的输入框)。一个逻辑字段可以渲染成多个物理控件(如区间选择),多个物理控件也可以归属同一逻辑字段。Schema 的 field 代表逻辑字段,VFormCol 的数量代表物理控件。保持这层心智模型,能避免后期在"数据键"和"界面元素"之间反复纠结。
七、嵌套 VFormItem 渲染子结构
当 FormSchema 自身带 schema 子数组(如"收货地址"包含省/市/详细地址),VFormItem 在渲染完自身控件后,还要递归渲染子 schema 里的每一项——本质上是在 VFormItem 内部再放一组 VFormItem。递归渲染保证了任意深度的字段树都能被同一套组件消费,而不需要为每一层嵌套单独写模板。关键在于递归时把子项的 field 拼到父路径上,保证最终 model 的嵌套结构与 Schema 一致。
<template v-if="item.schema?.length">
<VFormItem
v-for="(sub, i) in item.schema"
:key="i"
:item="sub"
v-model="model[sub.field]"
/>
</template>八、布局变化与校验展示的耦合
布局层(尤其是横向并排、跨列)会改变 FormItem 在页面上的视觉宽度,进而影响其内部错误提示、label 宽度、必填星号的位置表现。动态布局越复杂,越要留意 VFormItem 的 label-width、error 插槽在不同 span 下的表现是否一致。布局与校验展示不是完全正交的两件事,跨列表单尤其要在多种宽度下回归校验提示的可见性。
九、响应式 span 与栅格断点
VFormCol 的 span 不应写死,真实后台常常需要在窄屏下让字段整行铺满、宽屏下多列并排。可以借鉴栅格系统的响应式断点(如 :span="12" 在中等屏占半行、小屏占满),把断点配置也放进 Schema 的 colProps。这样布局描述依旧集中在配置里,组件只负责消费,避免把响应式逻辑散落到各页面。
{
field: "keyword",
label: "关键词",
type: "Input",
colProps: { span: 24, md: 12, lg: 8 }
}十、与表格布局抽象的对照
表格里我们用 VTableColumn 递归描述多级表头,表单里用 VFormItem 递归描述嵌套字段,两者都是"用同一组件消费树状配置"的范式。区别是表格的子节点是"列",表单的子节点是"字段",且表单子节点往往带自己的 label 和校验。理解这种对照,能让你在维护两套组件时复用同一套递归心智,而不是各写一套独立的遍历逻辑。
十一、布局方案取舍小结
把本章关于布局的结论固化,避免后续在 VFormItem 里又偷偷写布局逻辑:
- 字段渲染与列布局必须分层:
VFormItem不管排布,VFormCol才管排布 - 选项型子节点用
children描述,不在type上硬塞结构信息 - 逻辑字段与物理控件始终区分:
field决定数据键,VFormCol数量决定控件数 - 嵌套字段用递归渲染,子项
field拼父路径,保证model结构与 Schema 一致 - 响应式断点也进配置(
colProps),不要让页面层散写布局逻辑
这套三层结构(容器 / 字段 / 列)未来还会遇到更外层的 LayoutWrapper,届时依然遵循"层级越往外、包裹粒度越粗"的原则。布局能力的演进方向始终是"分层更细、配置更全",而不是把更多逻辑压进单一组件。
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
字段里并排控件导致 VFormItem 臃肿 | 布局逻辑和字段逻辑混写 | 抽 VFormCol 布局层 |
| 下拉框选项无法表达 | Schema 只有 type 没有子结构 | 加 children 描述选项 |
| 一行多控件数据键混乱 | 混淆逻辑字段与物理控件 | 明确 field 对应逻辑字段 |
| 布局改了连控件也跟着改 | 三层职责没拆开 | VForm/VFormItem/VFormCol 各司其职 |
| 区间选择不知道怎么建模 | 把视觉并排当成多个字段 | 一个逻辑字段 + 多个 VFormCol |
| 嵌套字段 model 结构错位 | 递归时没拼父路径 | 子项 field 拼到父路径上 |
| 跨列后错误提示看不见 | 布局与校验展示耦合被忽略 | 多宽度回归校验提示可见性 |