news 2026/9/23 15:01:30

Formily Next Space 组件指南:基于 Flex 的表单元素并排布局方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Formily Next Space 组件指南:基于 Flex 的表单元素并排布局方案
  • 前端
  • UI组件

【免费下载链接】formily

📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3

项目地址:https://gitcode.com/gh_mirrors/fo/formily
点击查看免费下载

Space 是 @formily/next 中基于 Flex 布局的轻量排版组件,用于将任意元素快速并排或纵向排列,是表单内“姓名分栏输入”“单位拼接”等场景的常用工具。阅读本文后,你将掌握 Space 的三种 Schema 写法(Markup Schema / JSON Schema / Pure JSX)、完整 API 参数语义,以及它与 FormLayout.spaceGap 联动、默认尺寸映射等底层实现原理。

Space 组件在 Formily 生态中承担着“无数据节点编排”的职责:它本身不绑定任何表单字段,而是作为一个纯布局容器,将多个字段横向或纵向排布在同一视觉行内。与FormGrid(栅格系统)不同,Space 关注的是紧凑、等间距的元素序列,非常适合姓 + 名、单位 + 数值、多个附加后缀输入框等场景。

一、组件定位:Void 节点上的布局容器

从源码结构看,Space 是一个不产生字段值的布局组件。在 packages/next/src/space/index.tsx 中,它接收children后通过toArray将子元素展开,并为每个子元素包裹space-item容器:

{toArray(props.children, { keepEmpty: true }).map((child, index) => ( <div className={`${prefix}-item`} key={index}> {child} </div> ))}

因此在使用时,Space 必须挂载在VoidField / void 类型节点上(x-component="Space"),其子节点才是真正参与表单的字段。这保证了布局节点不会污染表单数据模型,是 Formily “布局与数据分离”设计理念的典型体现。

二、Markup Schema 用法

Markup Schema 是 Formily React 中最直观的声明式写法。将Space注册进createSchemaField的 components 后,即可在SchemaField.Void上使用:

import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, Space, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <SchemaField> <SchemaField.Void title="name" x-decorator="FormItem" x-decorator-props={{ asterisk: true, feedbackLayout: 'none', }} x-component="Space" > <SchemaField.String name="firstName" x-decorator="FormItem" x-component="Input" required /> <SchemaField.String name="lastName" x-decorator="FormItem" x-component="Input" x-visible="{{$values.firstName === '123'}}" required /> <SchemaField.String name="kk" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> </SchemaField.Void> <SchemaField.Void title="Text concatenation" x-decorator="FormItem" x-decorator-props={{ asterisk: true, feedbackLayout: 'none', }} x-component="Space" > <SchemaField.String name="aa" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> <SchemaField.String name="bb" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> <SchemaField.String name="cc" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> </SchemaField.Void> <SchemaField.String name="textarea" title="text box" x-decorator="FormItem" required x-component="Input.TextArea" x-component-props={{ style: { width: 400, }, }} /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> )

要点解析:

  • 每个SchemaField.Voidx-component="Space"声明布局容器,x-decorator="FormItem"让整个 Space 拥有统一的标签与校验反馈区域;
  • 子字段通过name独立绑定表单数据,彼此互不影响;
  • x-visible="{{$values.firstName === '123'}}"演示了字段联动:当 firstName 的值等于'123'时,lastName 才显示,这验证了 Space 内的字段依然完整参与 Formily 的响应式联动体系;
  • addonAfter: 'Unit'用于给输入框追加单位后缀,形成“数值 + 单位”的拼接观感。

三、JSON Schema 用法

当 Schema 以纯 JSON 形式存在(如后端下发、动态渲染)时,Space 的声明方式转换为type: 'void'+'x-component': 'Space'

import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, Space, }, }) const form = createForm() const schema = { type: 'object', properties: { name: { type: 'void', title: 'Name', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { firstName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, lastName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, }, }, texts: { type: 'void', title: 'Text concatenation', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { aa: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: 'Unit', }, 'x-component': 'Input', required: true, }, bb: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: 'Unit', }, 'x-component': 'Input', required: true, }, cc: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: 'Unit', }, 'x-component': 'Input', required: true, }, }, }, textarea: { type: 'string', title: 'Text box', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': { style: { width: 400, }, }, required: true, }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> )

JSON Schema 与 Markup Schema 在语义上完全等价:type: 'void'对应 Markup 中的SchemaField.Voidproperties对应 JSX 子节点。这种等价性让同一份表单既能静态写在 JSX 里,也能由后端动态下发渲染,是 Formily 动态表单能力的核心。

四、Pure JSX 用法

如果不想使用 Schema 体系,可以直接用@formily/reactField/VoidField以命令式 JSX 构建等价表单:

import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field, VoidField } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <VoidField name="name" title="name" decorator={[ FormItem, { asterisk: true, feedbackLayout: 'none', }, ]} component={[Space]} > <Field name="firstName" decorator={[FormItem]} component={[Input]} required /> <Field name="lastName" decorator={[FormItem]} component={[Input]} required /> </VoidField> <VoidField name="texts" title="Text concatenation" decorator={[ FormItem, { asterisk: true, feedbackLayout: 'none', }, ]} component={[Space]} > <Field name="aa" decorator={[ FormItem, { addonAfter: 'Unit', }, ]} component={[Input]} required /> <Field name="bb" decorator={[ FormItem, { addonAfter: 'Unit', }, ]} component={[Input]} required /> <Field name="cc" decorator={[ FormItem, { addonAfter: 'Unit', }, ]} component={[Input]} required /> </VoidField> <Field name="textarea" title="text box" decorator={[FormItem]} component={[ Input.TextArea, { style: { width: 400, }, }, ]} required /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> )

Pure JSX 写法中,VoidFieldcomponent={[Space]}声明布局组件,decorator={[FormItem, {...}]}声明装饰器及参数,Fieldcomponent={[Input]}声明字段组件。它与 Schema 写法共享同一个底层字段模型,三种写法可在同一表单中混用。

五、API 详解与源码级实现原理

5.1 属性总览

Space 的属性定义位于 packages/next/src/space/index.tsx 的ISpaceProps接口,完整参数如下:

属性名类型说明默认值
styleCSSProperties自定义样式-
classNamestring自定义 class 名-
prefixstring样式前缀true
sizenumber \|'small' \|'large' \|'middle'子元素间隔大小8px
direction'horizontal' \|'vertical'排列方向'horizontal'
align'start' \|'end' \|'center' \|'baseline'交叉轴对齐方式'start'
wrapboolean是否自动换行false

5.2 size:间隔尺寸的三种解析路径

size的解析逻辑直接体现了 Space 与 FormLayout 的联动设计:

const spaceSize = { small: 8, middle: 16, large: 24, } const _size = size ?? layout?.spaceGap ?? 8
  • 显式传值:传入数字(如size={12})时直接作为间隔像素;传入枚举字符串时通过spaceSize映射(small=8px、middle=16px、large=24px);
  • 继承 FormLayout:未传size时,自动读取useFormLayout()返回的spaceGap,即上层 FormLayout 的spaceGap?: number配置(见其IFormLayoutProps接口),实现整表统一间距;
  • 兜底默认值:两层都没有时回退到8px

最终间隔通过isNumberLike判定后传给@alifd/nextBox组件的spacing属性落地。

5.3 direction 与 align:Flex 方向与对齐

directionalign会被转换为 Box 的 Flex 语义:

const getDirection = () => (direction === 'horizontal' ? 'row' : 'column') const getAlign = () => { if (align === 'start') return 'flex-start' if (align === 'end') return 'flex-end' return 'center' }
  • direction='horizontal'row(横向并排,默认),verticalcolumn(纵向堆叠);
  • align映射到 Flex 的align-itemsstartflex-start(默认)、endflex-endcentercenterbaseline交由 Box 原生处理;
  • 容器根元素强制display: 'inline-flex',因此 Space 可以像内联元素一样被嵌入到文本流或按钮组中,同时内部保持 Flex 布局。

5.4 prefix 与样式细节

usePrefixCls('space', props)生成space样式前缀(可通过prefix覆盖),每个子元素被包裹在<div class="xxx-space-item">中。对应样式 packages/next/src/space/main.scss 只有一个关键规则:

.#{$space-prefix-cls}-item:empty { display: none !important; }

空子元素自动隐藏。这意味着当联动导致某个字段被隐藏(如示例中 lastName 的x-visible条件不满足)时,它在 Space 中不会留下空白占位,布局不会出现空洞。样式入口见 packages/next/src/space/style.ts,它引入@alifd/next/lib/box/stylemain.scss

5.5 与 FormLayout 的联动:spaceGap

这是 Space 最值得关注的继承特性:在 packages/next/src/form-layout/index.tsx 中,FormLayout通过FormLayoutDeepContext/FormLayoutShallowContext双层 Context 向下传递布局配置(useFormLayout合并两层取值),spaceGap正是其中之一。因此你可以在FormLayout上统一设置:

<FormLayout spaceGap={12}> {/* 所有未显式传 size 的 Space 都会使用 12px 间隔 */} </FormLayout>

从源码结构看,这保证了“整表间距统一”只需一处配置即可生效,而单个 Space 仍可通过自身size覆盖全局值,兼顾全局一致性与局部定制。

5.6 多端实现对照

需要说明的是,Space 在各 UI 库适配包中均有实现,且保持一致的 API 心智:

  • @formily/antd(packages/antd/src/space/index.tsx):直接透传 antd 的Space组件,同样接入useFormLayoutspaceGap作为默认size
  • @formily/next(本文主体):基于@alifd/nextBox封装,增加directionalignwrap等语义化参数。

两者共享同一套size ?? spaceGap取值策略,切换 UI 库时无需改动业务用法。

六、典型使用建议

  • 姓名分栏:姓、名分别作为 Space 子字段,保持独立校验与数据键;
  • 数值 + 单位:利用addonAfter在输入框后拼接单位,多个字段并排形成“测量值序列”;
  • 联动隐藏:结合x-visible表达式动态显隐子字段,配合space-item:empty的隐藏规则,布局自动收拢,无需手动管理占位;
  • 整表间距:优先通过FormLayout.spaceGap统一配置,局部再以size覆盖,避免重复书写间距。

结语

Space 是 Formily 表单布局体系中“小而美”的一环:它用最小的 API 表面(7 个属性)覆盖了并排、纵排、对齐、换行与间距继承等日常布局需求,同时通过 Void 节点与 Flex 容器的组合,与 Formily 的联动、校验、数据模型体系无缝协作。理解它的实现(packages/next/src/space/index.tsx)与 FormLayout 的 Context 联动机制,能帮助你在实际表单项目中更准确地选择布局方案——当需要栅格化的复杂排布时选用FormGrid,当只需要紧凑的元素序列时,Space 就是最直接的选择。

  • 前端
  • UI组件

【免费下载链接】formily

📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3

项目地址:https://gitcode.com/gh_mirrors/fo/formily
点击查看免费下载
上一篇:agent-starter-pack enhance 命令实战指南:不新建目录,将现有项目原地升级为生产级 Agent
下一篇:Weaver:一款 Swift 语言的依赖注入框架

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 14:55:55

Java房屋租赁管理系统源码部署与二次开发实战指南

简介&#xff1a;这份资源是面向Java Web初学者与进阶开发者的房屋租赁管理系统完整源码包&#xff0c;适合用于课程设计、毕业设计或自学练手。系统围绕房源信息、租户资料、租赁合同、租金收取、费用计算与到期提醒等业务模块展开&#xff0c;帮助理解Java在实际管理类项目中…

作者头像 李华
网站建设 2026/9/23 14:50:47

电商后台系统怎么做?零代码搭建经营看板的完整指南(2026最新)

摘要&#xff1a;电商后台系统越做越重&#xff0c;问题往往不在功能多少&#xff0c;而在数据有没有被用起来。本文结合2026年最新实践&#xff0c;讲清如何用零代码把后台数据变成经营看板&#xff0c;减少重复劳动、更快做决策。 很多老板跟我聊后台的时候&#xff0c;都会…

作者头像 李华
网站建设 2026/9/23 14:50:08

flutter_tts鸿蒙适配实战:OpenHarmony语音服务桥接指南

1. 为什么“Flutter 鸿蒙化”不是口号&#xff0c;而是必须直面的工程现实最近三个月&#xff0c;我连续接手了三个客户项目&#xff0c;需求清一色是&#xff1a;“用 Flutter 写的 App&#xff0c;现在要上 OpenHarmony 设备”。没有例外&#xff0c;没有商量余地。其中两个项…

作者头像 李华
网站建设 2026/9/23 14:50:06

平价龙虾选购与烹饪全攻略

1. 龙虾美食的平价获取之道作为一个在沿海城市生活了十年的资深吃货&#xff0c;我发现获取新鲜龙虾其实有很多不为人知的省钱技巧。很多人以为龙虾是高端食材&#xff0c;动辄几百元一斤&#xff0c;但实际上只要掌握正确方法&#xff0c;完全可以用平民价格享受这份美味。龙虾…

作者头像 李华
网站建设 2026/9/23 14:49:49

SAP采购退货操作全流程:MIGO 161移动类型与凭证处理实战

简介&#xff1a;一套讲解SAP系统退货操作流程的培训PPT&#xff0c;面向企业采购、仓储及供应链相关岗位人员&#xff0c;帮助用户熟悉库区及料废退货&#xff08;移动类型161&#xff09;与待检区退货&#xff08;移动类型124&#xff09;两大场景的完整操作。课件以实际系统…

作者头像 李华