在中后台开发领域,表单是当之无愧的 "核心场景载体"------ 无论是数据录入、信息查询,还是流程审批、配置管理,绝大多数业务需求都需要通过表单来落地。可以说,表单的开发效率、性能表现与可维护性,直接决定了中后台项目的整体研发质量,其重要性不言而喻。
与此同时,中后台表单场景的复杂性也远超普通 UI 组件,实际开发中常面临多重痛点:
- 字段层面:字段数量往往多达数十个,且字段间关联逻辑复杂(如 "选择 A 选项后显示 B 字段""C 字段值依赖 D 字段计算")。这既要求性能不随字段数量增加而下降,又需要简化联动逻辑的实现,避免因频繁触发更新导致的性能损耗;
- 数据与状态管理层面:数据上需处理前后端格式差异(如前端布尔值与后端字符串 "0/1" 的转换)、跨表单数据通信(如弹窗表单与主表单的状态同步);状态上则要应对动态自增表单项(如多行联系人信息)、字段状态回滚等复杂需求,极易出现状态混乱;
- 扩展性层面:随着业务迭代,表单常需支持动态配置化,以适配不同业务线的定制化需求,这对表单库的灵活度提出了更高要求。
在 React 社区中,开发者针对这些痛点提出了众多表单解决方案。我在日常工作中主要使用 Ant Design 组件库,其 Form 组件凭借易用性和完整性成为主流选择。今天,我准备从 Ant Design Form 组件的底层依赖入手,探索其背后的核心实现 ------rc-field-form。
不过在聊 rc-field-form 之前,有一个 "前辈" 不得不提,那就是 rc-form。这两个库同为 Ant Design Form 组件的底层支撑:rc-form 是 Ant Design 3 版本的依赖,支撑了早期类组件时代的表单开发;而 rc-field-form 则是 Ant Design 4 版本及以后的核心依赖,针对函数组件生态做了全面优化,解决了 rc-form 的诸多痛点。
rc-form:React 表单库的经典架构与设计启示
rc-form 曾是 React 类组件时代被广泛使用的基础库,也是 Ant Design 3 版本表单的核心依赖。它通过 getFieldDecorator 方法实现表单项组件与状态的绑定,支持字段校验、统一管理等基础功能。但随着前端应用复杂度提升,其设计短板逐渐暴露:
- 基于高阶组件(HOC)模式封装,与当前主流的函数组件适配性欠佳;
- 表单状态管理与 UI 渲染耦合较深,在处理动态增减表单项、跨字段联动等场景时,容易出现性能瓶颈和逻辑混乱;
- 存在全量渲染的性能问题:当某个表单字段状态变化时,会触发整个表单组件重新渲染,而非仅更新发生变化的字段。
那么,既然 rc-form 已是一个相对落后的库,为何我们还要在此提及它?
主要是因为它的许多设计堪称经典,值得细细品味。用 "以字段为核心的状态管理" 和 "声明式 API 与底层逻辑的解耦" 来描述其设计,再贴切不过。
rc-form 借助高阶组件、装饰器模式及单一职责等设计理念,既简化了表单开发的复杂度,又保留了足够的灵活性,成为早期 React 表单库的标杆实现。其设计思想也深刻影响了后续的 rc-field-form、Formily、React Hook Form 等同类库。
我们不妨打开其源码(github.com/react-compo...),简单品味它 "以字段为核心的状态管理" 与 "声明式 API 与底层逻辑解耦" 这两大经典设计。

上图为rc-form源码目录。
声明式 API 与底层逻辑的解耦
rc-form的源码十分精炼,总共才1200行。
合理的分层设计,使其实现了声明式 API 与底层逻辑的解耦。可分为这三个主要部分:核心层、扩展层、接入层。

核心层
核心层是整个库的根基,承载着表单运行的核心机制,它的实现不依赖特定框架,主要包含以下模块:
- 表单工厂(createBaseForm.js):该文件暴露创建表单实例的方法,是所有表单能力的 "入口"。它通过高阶组件(HOC)模式,向用户传入的表单组件注入表单方法,创建状态仓库(store)实例,并管理组件生命周期。
- 状态管理(createFieldsStore.js):该文件暴露的方法用于创建store实例 ------ 这是字段状态管理的核心 "容器",负责存储所有字段的关键信息(如值 value、元数据 meta 等),并提供 getField(获取字段)、setField(更新字段)、resetFields(重置字段)等原子化操作方法。
- 校验引擎:基于
async-validator
库封装,支持处理同步、异步两种校验场景,为字段合法性校验提供底层能力。
扩展层
扩展层基于核心层能力进行功能延伸,按需补充特定场景的实用特性(如 DOM 交互相关能力),主要包括:
- 基础功能扩展:实现了
createForm
函数,该函数新增了批量操作方法(getFieldsValue / setFieldsValue)和表单提交状态管理( isSubmitting )等。 - DOM 交互扩展(
createDOMForm
):提供与 DOM 相关的增强能力,其中最经典的功能是 "校验失败时自动滚动到错误字段",提升用户交互体验。
接入层
接入层为开发者直接提供使用的 API。
- 字段绑定工具(getFieldDecorator):这是 Ant Design 3 时期最常用的 API,本质是一个高阶装饰器。它能将普通输入组件与表单库绑定,并自动注入 value / onChange 逻辑,实现字段值的双向同步。
- 字段包装组件:负责处理字段的挂载 / 卸载生命周期,维护 DOM 元素引用与表单状态的关联,确保字段状态与 DOM 行为同步。
以字段为核心的状态管理
在表单场景中,字段数量往往较多。若这些字段的状态都由开发者手动控制,不仅代码冗余,还容易出现状态同步混乱的问题,体验会十分糟糕。
rc-form 则通过创建 FieldsStore
,将所有字段的状态(包括值、校验信息、字段元数据等)集中管理,相当于为表单状态搭建了一个 "统一仓库"。当任意字段状态发生变化时(比如通过 setFields
主动修改,或用户操作触发 onChange
),rc-form 会启动更新流程,调用 forceUpdate
强制整个表单组件重新渲染,以此实现字段状态与 UI 的同步。
字段状态的变化可分为两个阶段 "初始化阶段" 和 "交互阶段" ,下图清晰展示了这两个阶段中 rc-form 的核心处理逻辑:

初始化阶段
rc-form 在创建 form 实例后,会立即初始化 fieldsStore
作为字段状态的集中管理容器。所有通过 getFieldDecorator
声明的表单项,都会被注册到这个 fieldsStore
中;同时,这些表单项会被 FieldElemWrapper
组件包裹,自动注入 value
、onChange
等属性以实现双向绑定 ------ 其中 onChange
事件经过特殊处理,会成为触发 rc-form 渲染流程的入口。
交互阶段
当用户输入触发表单字段的 onChange
事件时(因已被特殊处理),rc-form 的渲染流程会被启动:首先 fieldsStore
调用 setFields
方法更新对应字段的值,随后触发一系列内部处理逻辑,最终调用表单组件的 forceUpdate
方法强制渲染。 这一更新机制正是其性能问题的根源:任何字段的状态变化,都会触发 fieldsStore
更新并通过 forceUpdate
导致整个表单组件重新渲染,即使其他未变化的字段也会随之重新渲染。
小结
尽管目前 rc-form 因技术迭代逐渐退出主流,但它的设计思想深刻影响了后续表单库的发展:
- 集中式状态管理的合理性:以
FieldsStore
作为表单状态 "单一数据源" 的设计,被 Formily、React Hook Form、rc-field-form 等众多主流表单库继承,成为表单状态管理的经典范式; - 声明式 API 降低使用成本:
getFieldDecorator
通过参数配置实现声明式开发的设计,大幅简化了表单字段绑定逻辑,如今已成为现代表单库的标配能力; - 分层架构的扩展性:核心逻辑层(如状态管理、校验)与接入层(如
getFieldDecorator
)分离的架构,为现代库的 "插件化扩展" 提供了关键思路,让功能迭代更灵活。
同时,rc-form 暴露的短板,也为后续表单库指明了明确的优化方向:
- 适配函数组件:用 React Hooks 替代高阶组件(HOC),贴合当前函数组件为主的开发模式,降低使用门槛;
- 优化渲染性能:实现字段级渲染控制,仅让状态变化的字段及依赖组件重新渲染,彻底解决全量渲染导致的性能问题;
- 简化核心 API:减少对装饰器模式的依赖,设计更直观的 API(如
useForm
),降低开发者的理解和使用成本。
作为 React 类组件时代的经典表单库,rc-form 曾成功解决了当时表单开发的核心痛点 ------ 状态分散、同步繁琐、逻辑耦合等问题。它的 "功成身退" 并非设计失败,而是前端技术迭代的必然结果;其 "以字段为核心" 的状态管理理念、"解耦底层逻辑与上层使用" 的架构思维,至今仍是前端组件库设计的重要参考,堪称 "旧时代技术留给新时代的启示"。
rc-field-form : 基于 Hooks 重构的现代化表单库
rc-form 存在的短板(如依赖高阶组件 HOC、全量渲染导致性能损耗、API 设计偏繁琐等),直接推动了 rc-field-form 向更优方向迭代 ------ 它针对性地强化了精细化状态管理、实现了高性能渲染、简化了核心 API,支持 React Hooks。这些改进让 rc-field-form 更适配现代 React 应用的开发需求,尤其在大型表单场景中,性能表现和开发体验都有了明显提升。
若要深入理解 rc-field-form 的实现原理,我们仍需从其源码切入,可参考官方仓库:github.com/react-compo... 。

上图为 rc-filed-form 的代码目录。
rc-field-form 在继承 rc-form 经典设计的同时,又解决了诸多 rc-form 的短板。以下我将从分层设计、主要模块和更新流程三个方面进行探索:
一、分层设计
作为 rc-form 的升级版,rc-filed-form 依然沿用了"解耦底层逻辑与上层使用" 的架构思想。

1、核心层
与 rc-form 逻辑相同,rc-field-form 的核心层是整个库的根基,负责管理表单核心状态(如字段值、元信息等)与核心逻辑(如值的读写操作)。它不依赖任何 UI 框架,仅专注于数据处理与逻辑管控,核心能力包括:
- 状态管理:通过 FormStore 单例统一管理整个表单的关键状态,如字段值(value)、已注册的字段实例(fieldEntities)等,确保状态来源唯一;
- 值操作 API:提供 getFieldValue(获取单个字段值)、setFieldValue(更新单个字段值)、getFieldsValue(批量获取字段值)、setFieldsValue(批量更新字段值)等基础方法,覆盖字段值的常用操作;
- 字段注册与通知:初始化阶段通过 registerField 方法收集并注册所有字段;当字段值发生变化时,调用 notifyObservers 方法通知所有已注册字段,触发字段自身的 onStoreChange 逻辑,由字段自主判断是否需要重新渲染(re-render)。
2、扩展层:丰富表单能力的 "插件"
扩展层基于核心层能力构建,旨在补充表单场景所需的扩展功能(如校验、字段联动、格式处理等)。它不修改核心逻辑,仅通过 "功能增强" 或 "场景适配" 的方式扩展能力,核心包括:
-
校验扩展:内置必填、格式(如手机号、邮箱)等基础验证规则,同时支持自定义验证函数(
validator
),满足复杂场景下的字段合法性校验需求; -
依赖管理:rc-form 因全量渲染无需考虑字段依赖,而 rc-field-form 为实现 "字段级精准更新",必须完善依赖管理 ------ 例如 A 字段值依赖 B 字段,当 B 变化时 A 需同步更新:
- 通过
dependencies
配置项定义字段依赖关系; - 核心层的
notifyObservers
方法会根据依赖关系,精准通知关联字段触发重渲染与重新校验,避免无关字段更新。
- 通过
3、接入层:连接 React 组件的 "桥梁"
接入层承担着核心层底层逻辑与 React 生态的 "衔接" 职责,它对外提供开发者可直接调用的 React 组件与 Hook,将复杂的底层状态管理细节完全屏蔽,大幅降低了使用门槛。其中,核心 Hook 为 useForm,核心组件则包括 Form(表单容器)与 Field(字段包装组件)。
二、主要模块
在分析完 rc-field-form 的各层设计后,我们来看一个实际使用案例。该案例中用到了 useForm
、Form
组件与 Field
组件 ------ 这三者正是 rc-field-form 接入层的核心模块,能直观体现其使用逻辑。
ini
import Form, { Field } from 'rc-field-form';
import React from 'react';
import Input from './components/Input';
export default () => {
const [form] = Form.useForm();
return (
<Form
form={form}
preserve={false}
onFieldsChange={fields => {
console.error('fields:', fields);
}}
>
<Field name="name">
<Input placeholder="Username" />
</Field>
<Field<FormData> dependencies={['name']}>
{() => {
return form.getFieldValue('name') === '1' ? (
<Field name="password">
<Input placeholder="Password" />
</Field>
) : null;
}}
</Field>
<button type="submit">Submit</button>
</Form>
);
};
来看各个的核心模块的具体实现
FormStore 模块(useForm.ts)
该模块负责创建表单的数据仓库 store。我对源码做了部分精简并添加注释,使其更易读。从代码结构可看出,该模块主要包含两部分:useForm
和 FormStore
。
- FormStore 类: 其内部属性(如 store、fieldEntities 等)用于存储字段相关信息;提供 getFieldValue(获取字段值)、setFieldValue(设置字段值)等方法操作 store 中的数据。核心的 notifyObservers 方法承担通知职责 ------ 每当通过 setFieldsValue 等方法修改 store 后,会触发该方法遍历所有已注册的字段实体,调用其 onStoreChange 方法以同步状态。
- useForm Hook: 作为创建入口,用于初始化 Form 实例与 FormStore 实例,并通过 React 机制确保 FormStore 实例为单例(全表单共享同一状态仓库)。
js
import * as React from 'react';
import { getNamePath, getValue, setValue } from './utils/valueUtil';
/**
* 表单数据仓库类
* 负责管理表单的状态、字段注册、值的更新等核心逻辑
*/
class FormStore {
// 表单当前值存储(键值对形式,如 { username: 'xxx', age: 20 })
private store = {};
// 表单初始值存储
private initialValues = {};
// 已注册的字段实体列表(每个字段对应一个Field组件实例)
private fieldEntities = [];
// 强制重新渲染的函数(由React状态更新触发)
private forceUpdate: () => void;
constructor(forceUpdate: () => void) {
// 用于触发表单重新渲染的函数
this.forceUpdate = forceUpdate;
}
//对外暴露表单表单实例方法,就是我们经常使用的formRef
getForm = () => ({
getFieldValue: this.getFieldValue, // 获取单个字段值
setFieldValue: this.setFieldValue, // 设置单个字段值
getFieldsValue: this.getFieldsValue, // 获取所有字段值
setFieldsValue: this.setFieldsValue, // 批量设置字段值
resetFields: this.resetFields, // 重置字段值
_init: true, // 标记表单已初始化
getInternalHooks: this.getInternalHooks, // 获取内部钩子(供组件内部使用)
});
// 内部钩子方法提供给表单内部组件(如Field)注册和初始化的接口
private getInternalHooks = () => ({
registerField: this.registerField, // 注册字段实体
setInitialValues: this.setInitialValues, // 设置初始值
});
//注册字段实体
private registerField = (entity) => {
// 将字段实体添加到列表中
this.fieldEntities.push(entity);
// 返回取消注册的函数(用于组件卸载时清理)
return () => {
this.fieldEntities = this.fieldEntities.filter(item => item !== entity);
};
};
//设置初始值
private setInitialValues = (value) => {
this.initialValues = { ...values }; // 保存初始值
this.store = { ...this.store, ...values }; // 初始化当前值
};
//获取单个字段值
private getFieldValue = (name) => {
const namePath = getNamePath(name); // 统一转换为内部路径格式(数组)
return getValue(this.store, namePath); // 从存储中获取值
};
//获取所有字段值
private getFieldsValue = () => ({ ...this.store }); // 返回当前存储的拷贝
// 设置单个字段值
private setFieldValue = (name, value: any) => {
const namePath = getNamePath(name); // 转换为内部路径
const prevStore = { ...this.store }; // 保存更新前的状态
this.store = setValue(this.store, namePath, value); // 更新存储中的值
// 通知所有字段实体存储已更新
this.notifyObservers(prevStore, [namePath], { type: 'valueUpdate' });
};
//批量设置字段值
private setFieldsValue = (values) => {
const prevStore = { ...this.store }; // 保存更新前的状态
// 遍历所有值并更新存储
Object.entries(values).forEach(([key, value]) => {
this.store = setValue(this.store, getNamePath(key), value);
});
// 通知所有字段实体存储已更新
this.notifyObservers(prevStore, null, { type: 'valueUpdate' });
};
//通知所有字段
private notifyObservers = (prevStore, namePathList, info) => {
// 遍历所有字段实体,调用其onStoreChange方法
this.fieldEntities.forEach(entity => {
entity.onStoreChange(prevStore, namePathList, { ...info, store: this.store });
});
};
}
export default function useForm(form?: any) {
// 创建一个用于强制更新的React状态
const [, forceUpdate] = React.useState({});
// 用ref保存表单实例(避免每次渲染重新创建)
const formRef = React.useRef<any>();
// 初始化表单实例(只在首次渲染时执行)
if (!formRef.current) {
if (form) {
// 如果传入了外部表单实例,直接使用
formRef.current = form;
} else {
// 否则创建新的表单仓库实例
const formStore = new FormStore(() => {
// 强制更新的回调(通过更新React状态触发重渲染)
forceUpdate({});
});
formRef.current = formStore.getForm();
}
}
// 返回表单实例
return [formRef.current];
}
Form 模块(Form.tsx )
就是是我们经常使用的Ant Design的Form组件,它是表单容器组件,负责初始化表单实例、管理上下文等。
- 它接收 initialValues(表单初始值)、form(外部传入的表单实例)等配置,通过 useForm 钩子创建新的表单实例或复用外部实例,并借助 FieldContext(上下文)将实例传递给所有子组件,确保子表单项能共享表单状态;
- 组件的 children 属性即开发者传入的具体表单项(如输入框、选择器等),这些子项通过上下文获取表单实例,实现与全局状态的绑定。
js
import * as React from 'react';
import useForm from './useForm';
import FieldContext, { HOOK_MARK } from './FieldContext';
import FormContext from './FormContext';
import { isSimilar } from './utils/valueUtil';
import ListContext from './ListContext';
/**
* 表单组件核心
* 负责管理表单整体状态、上下文传递和事件处理
*/
const Form = (
{
name,
initialValues,
fields,
form: userForm,
children,
validateTrigger = 'onChange',
clearOnDestroy,
...restProps
},
ref,
) => {
// 原生表单元素引用
const nativeElementRef = React.useRef<HTMLFormElement>(null);
// 获取全局表单上下文
const formContext = React.useContext(FormContext);
// 初始化表单实例(复用用户传入的或创建新的)
const [formInstance] = useForm(userForm);
// 获取内部钩子(用于表单内部状态管理)
const {
useSubscribe,
setInitialValues,
setCallbacks,
setValidateMessages,
setPreserve,
destroyForm,
} = formInstance.getInternalHooks(HOOK_MARK);
// 暴露表单实例到ref
React.useImperativeHandle(ref, () => ({
...formInstance,
nativeElement: nativeElementRef.current,
}));
// 注册表单到全局上下文
React.useEffect(() => {
if (name) {
formContext.registerForm(name, formInstance);
}
return () => {
if (name) {
formContext.unregisterForm(name);
}
};
}, [formContext, formInstance, name]);
// 初始化表单值(仅首次挂载时)
const mountRef = React.useRef(false);
if (!mountRef.current) {
setInitialValues(initialValues || {}, true);
mountRef.current = true;
}
// 组件卸载时清理
React.useEffect(
() => () => {
destroyForm(clearOnDestroy);
},
[destroyForm, clearOnDestroy],
);
// 处理子元素渲染 (使用者传入的组件)
let childrenNode: React.ReactNode;
if (typeof children === 'function') {
// 渲染函数模式:传入当前表单值和实例
const values = formInstance.getFieldsValue(true);
childrenNode = (children )(values, formInstance);
} else {
// 普通子元素模式
childrenNode = children;
}
// 监听fields变化并更新表单
const prevFieldsRef = React.useRef([]);
React.useEffect(() => {
if (!isSimilar(prevFieldsRef.current, fields || [])) {
formInstance.setFields(fields || []);
prevFieldsRef.current = fields || [];
}
}, [fields, formInstance]);
// FieldContext上下文
const formContextValue = React.useMemo(
() => ({
...(formInstance),
validateTrigger,
}),
[formInstance, validateTrigger],
);
// 包裹字段上下文和列表上下文的节点
const wrapperNode = (
<ListContext.Provider value={null}>
<FieldContext.Provider value={formContextValue}>
{childrenNode}
</FieldContext.Provider>
</ListContext.Provider>
);
// 使用指定组件作为包裹容器
return (
<Component
{...restProps}
ref={nativeElementRef}
// 处理表单提交
onSubmit={(event) => {
event.preventDefault(); // 阻止默认提交
event.stopPropagation();
formInstance.submit(); // 触发表单提交逻辑
}}
// 处理表单重置
onReset={(event) => {
event.preventDefault();
formInstance.resetFields(); // 触发表单重置
restProps.onReset?.(event); // 调用用户重置回调
}}
>
{wrapperNode}
</Component>
);
};
export default Form;
Field 模块(Field.tsx)
作为表单项的基础组件,Field 是实现表单控件(如输入框、选择器等)与表单状态双向绑定的核心 ------ 所有表单组件都需要被 Field 包裹才能接入表单系统。
- 初始化阶段(componentDidMount),Field 会将当前字段注册到 FormStore 中,同时建立数据变化的监听机制,确保表单状态变更时能同步到 UI;
- 核心方法 onStoreChange 负责响应状态更新:当字段关联的状态变化时,它会调用 forceUpdate 强制当前 Field 组件重新渲染,实现 UI 与状态的同步;
- getControlled 方法则专门处理控件的交互逻辑,其中对 onChange 事件做了特殊封装,使其触发时能接入 rc-field-form 的更新流程(如更新 FormStore 状态、通知关联字段等),形成完整的双向绑定闭环。
js
import React from 'react';
/**
* 表单字段组件
* 负责管理单个表单字段的状态、验证和与表单仓库的交互
*/
class Field extends React.Component {
static contextType = FieldContext;
state = {
resetCount: 0
};
private mounted = false; // 组件是否已挂载
private cancelRegisterFunc: (() => void) | null = null; // 取消注册的函数
private touched = false; // 字段是否被触碰过
private dirty = false; // 字段值是否被修改过
constructor(props) {
super(props);
// 初始化时将字段初始值注册到表单仓库
if (props.fieldContext) {
const { initEntityValue } = props.fieldContext.getInternalHooks();
initEntityValue(this);
}
}
componentDidMount() {
this.mounted = true;
// 注册字段实例到表单仓库
if (this.props.fieldContext) {
const { registerField } = this.props.fieldContext.getInternalHooks();
this.cancelRegisterFunc = registerField(this);
}
}
componentWillUnmount() {
this.mounted = false;
// 取消注册
if (this.cancelRegisterFunc) {
this.cancelRegisterFunc();
}
}
/**
* 获取字段的完整路径(包含前缀)
*/
getNamePath = () => {
const { name, fieldContext } = this.props;
const { prefixName = [] } = fieldContext;
return name ? [...prefixName, ...name] : [];
};
/**
* 获取当前字段的值
*/
getValue = (store?: Store): StoreValue => {
const { getFieldsValue } = this.props.fieldContext;
const namePath = this.getNamePath();
return getValue(store || getFieldsValue(true), namePath);
};
/**
* 处理表单仓库变化的回调
* 当仓库数据变化时更新字段状态
*/
onStoreChange = (prevStore, namePathList, info) => {
const prevValue = this.getValue(prevStore);
const curValue = this.getValue(info.store);
// 处理重置事件
if (info.type === 'reset') {
this.touched = false;
this.dirty = false;
this.forceUpdate();
return;
}
// 当值变化时更新状态
if (prevValue !== curValue) {
this.forceUpdate();
}
};
/**
* 获取受控组件属性(绑定value和onChange)
*/
getControlled = () => {
const {
trigger = 'onChange',
valuePropName = 'value',
getValueFromEvent,
fieldContext
} = this.props;
const namePath = this.getNamePath();
const value = this.getValue();
const { dispatch } = fieldContext.getInternalHooks(HOOK_MARK);
return {
[valuePropName]: value,
[trigger]: (...args: any[]) => {
// 标记为已触碰
this.touched = true;
this.dirty = true;
// 从事件中获取新值
const newValue = getValueFromEvent
? getValueFromEvent(...args)
: defaultGetValueFromEvent(valuePropName, ...args);
// 分发更新值的动作
if (newValue !== value) {
dispatch({
type: 'updateValue',
namePath,
value: newValue
});
}
}
};
};
render() {
const { children } = this.props;
const { resetCount } = this.state;
const meta = this.getMeta();
const controlledProps = this.getControlled();
// 处理渲染函数形式的子元素
if (typeof children === 'function') {
return (
<React.Fragment key={resetCount}>
{children(controlledProps, meta)}
</React.Fragment>
);
}
// 处理普通子元素(仅支持单个子元素)
const child = React.Children.only(children as React.ReactElement);
return React.cloneElement(child, {
...child.props,
...controlledProps
});
}
}
export default Field;
三、字段更新流程
和 rc-form 一样,rc-field-form 也通过创建状态仓库(FieldsStore)对所有字段的状态(包括值、校验信息等)进行集中管理。当任意字段状态发生变化时(比如通过 setFields 主动修改,或用户操作触发 onChange),表单项组件会调用 forceUpdate 强制更新。
二者的核心区别在于:rc-form 调用的是整个表单组件的 forceUpdate,导致全量渲染;而 rc-field-form 调用的是发生变化的表单项自身的 forceUpdate,实现了精准的字段级更新,从根本上优化了渲染性能。

1、初始化阶段
- 表单实例与状态仓库创建:通过 useForm Hook 创建新的表单实例(或复用外部传入的实例),同时初始化 filedStore 实例,作为所有字段状态的集中管理容器。
- 上下文注册与数据共享:创建 FormContext 和 FieldContext 两个上下文,用于向下传递表单实例、状态及核心方法,为内部 Field 组件提供跨层级数据访问能力,避免 props 透传冗余。
- 字段注册与属性注入:表单项首次挂载时,会自动注册到表单系统并记录到 filedStore 中;同时,所有表单项会被 WrapperField 组件包裹,自动注入 value、onChange 等关键属性 ------ 其中 onChange 经过特殊封装,确保触发时能接入 rc-field-form 的统一更新流程。
2、交互阶段
当通过 setFieldValue
主动修改字段值,或用户操作触发表单项的 onChange
事件时,会触发以下流程:
- 状态更新:表单项调用经过特殊处理的
onChange
方法,进而触发FormStore
的setFieldValue
方法,更新filedStore
中对应字段的状态; - 订阅通知触发:
FormStore
完成状态更新后,立即调用notifyWatch
方法,通知所有订阅了该字段变化的组件; - 依赖字段联动更新:根据
dependencies
配置的依赖关系,精准通知依赖当前字段的其他Field
组件,触发这些组件重新渲染与状态同步,实现字段间的联动效果。
小结
作为 rc-form 的迭代升级版本,rc-field-form 既继承了前者的核心设计思想,又针对函数组件时代的开发需求做了全面优化,最终成为 React 生态中轻量且灵活的表单解决方案。其核心优势可概括为三点:
- 分层架构的极致解耦:采用 "核心层(FormStore 管理状态与逻辑)- 扩展层(校验规则 / 字段联动)- 接入层(Field/Form 组件对接 UI)" 的三层设计,实现 "数据 - 逻辑 - UI" 的彻底分离 ------ 既保证核心逻辑可复用,又能为动态列表、跨表单联动等扩展场景提供灵活支撑;
- "字段实例化" 的状态管理:每个 Field 组件作为独立实例注册到 FormStore,通过
getNamePath
实现嵌套字段的精准定位。这一设计既解决了 rc-form 中 "装饰器模式" 的耦合问题,又能确保动态表单项的状态不丢失,目前已被 Ant Design Form 等主流组件库采用; - 轻量化的性能优化思路:摒弃 rc-form 中 "全量字段监听" 的模式,通过 "依赖字段更新"(
dependencies
配置)、"值对比触发渲染"(shouldUpdate
控制)等设计,让字段更新仅触发自身及关联组件重渲染。在保持轻量体积(无冗余依赖)的同时,完美适配复杂中后台表单场景。
rc-field-form 并非对 rc-form 的颠覆,而是 "继承式创新"------ 它保留了前者 "以字段为核心" 的状态管理思想,又通过分层解耦、性能优化、API 轻量化,解决了函数组件时代表单开发的效率与性能痛点。作为 Ant Design Form v4+ 的底层依赖,它不仅是一款优秀的表单库,更成为中后台表单 "分层设计""实例化状态管理" 的标杆,其 "轻量不简单、灵活不冗余" 的设计理念,至今仍是前端组件库设计的重要参考。
zero-form:业务模型驱动的现代化表单库
不过,即便 rc-field-form 已足够优秀,在日常工作中,尤其是中后台项目的重业务表单场景下,仅通过编写组件配置渲染表单的方式仍存局限。核心问题指向 "分离":rc-field-form 虽已实现内部 "组件渲染与状态管理的分离",但实际开发中,我们常将 "表单渲染逻辑" 与 "业务处理逻辑" 混在一起。随着需求迭代,代码容易逐渐 "腐化",最终演变为难以维护的 "屎山代码"。
日常需求多以业务为核心驱动,因此构建表单时,我们不应止步于编写表单项配置,而应进一步搭建专门的 "业务逻辑层";再结合业务驱动特性,我们应当构建的是"业务模型"------ 让表单渲染与业务逻辑彻底解耦,以此应对复杂多变的业务场景。
即便在开发过程中,开发者有意识地做分层设计,但在 "如何构建业务模型" 上仍存在认知差异:前后端解耦如何实现?逻辑与视图如何彻底分离?数据转换逻辑该写在哪、怎么写、写成什么形式?
我们认为,表单库应当主动引导开发者实现这种分层。这一分层思路与 Formily 有相似之处,但 Formily 并未规范业务逻辑层的实现方式;而我们希望提供一套标准,帮助用户落地业务逻辑层。正是基于上述思考,我们政采事业部前端团队正在开发一款 "业务模型驱动" 的现代化表单库,并将其命名为 zero-form,该表单库提供了基于分层架构的开发模式,隔离变化,提供稳定的业务模型。
基于上面的思考,我们需要解决如下几个核心问题 :
- 逻辑渲染隔离:如何实现业务逻辑与渲染逻辑的完全解耦
- 表单结构范式:如何规范表单结构的定义与书写标准
- 高性能动态渲染:如何解决大表单初始化与运行时的性能瓶颈,核心是实现字段级精准更新
- 依赖管理:如何围绕字段本身做管理,从 "主动修改联动字段" 转为 "监听联动字段、仅更新自身",让联动逻辑收拢到被联动字段,实现逻辑可测试、可迭代、可维护,并能快速分析字段依赖关系
1. 分层设计
zero-form 不仅延续 "UI 与状态分离" 的优势,更突破现有表单库局限,实现 UI 与业务逻辑的深度分离,最终构建稳定的前端视图模型 ------ 业务逻辑与 UI 完全解耦。其价值在于:API 接口层变更时仅需修改业务逻辑层,无需调整 UI;UI 重构时仅需更新组件,不影响业务逻辑。具体分层如下:
- UI 层:纯展示组件,仅接收字段配置和状态,不包含任何业务逻辑。提供了组件注册方法,可无缝切换 Ant Design、Element 等主流组件库。
- 状态层:承担数据存储、更新、依赖管理的核心职责,与主流表单库逻辑类似,通过单例 store 统一管理所有字段状态,并暴露修改 store 的方法,确保状态来源唯一;
- 业务模型层:业务逻辑的 "载体",与 UI 完全无关,本质是定义业务相关的数据结构与处理方法。zero-form 会暴露标准化的业务模型类,开发者需按规范实例化专属业务模型 ------ 包含模块实例化、数据初始化、表单结构生成、具体业务处理方法等核心能力。实例化后的业务模型将以
ref
形式暴露,供开发者调用。尽管这会增加部分初始代码量,但从长期迭代与稳定性来看,明确的业务模型能大幅降低需求变更成本,带来不可替代的灵活性。
表单结构范式
在 UI 层,zero-form 采用统一的表单结构描述协议(Schema),规范字段配置的书写方式,避免 "配置分散、格式混乱":
- Schema 以数组形式描述表单项,每个字段需包含标准化属性:id(唯一标识)、type(字段类型,如输入框、下拉框)、props(UI 配置,如占位符、样式)、rules(校验规则,如必填、格式校验)。
- 支持复杂结构:适配嵌套结构(如 object 嵌套子字段、array 动态列表)与动态字段(通过 dependencies 声明依赖关系),确保复杂表单的结构一致性。
- 生成逻辑统一:业务模型层通过 generateSchema 方法输出标准化 Schema,UI 层基于 Schema 自动渲染,无需手动拼接配置。
双开发模式
构建业务模型虽能应对复杂场景,但在简单需求下难免冗余。为此,zero-form 设计两种开发模式:
- Form 开发模式:面向简单场景,用法与 Ant Design Form 等库一致 ------ 直接通过配置项快速渲染表单,无需定义复杂业务模型,大幅降低轻量需求的开发成本。
- 业务模型开发模式:面向复杂业务场景,需先实例化业务模型搭建业务逻辑层,再通过配置表单项构建 UI 层,实现 "业务 - UI" 彻底解耦,支撑长期迭代与需求变更。
2. 高性能动态渲染
针对百级以上字段的大表单,zero-form 采用 "字段级按需更新" 策略,攻克初始化与运行时的性能瓶颈:
- 状态分片存储:按字段 id 分片存储状态,每个字段的状态(值、校验结果、UI 状态)独立管理,避免单状态对象过大导致的更新性能损耗。
- 精确依赖追踪:通过字段 dependencies 声明依赖关系,当某个字段更新时,仅重新计算其关联字段的状态(如是否可见、是否禁用),而非全表单重渲染,减少无效计算。
- 虚拟列表与延迟加载:对
array
类型的长列表表单,结合虚拟滚动技术,仅渲染可视区域内的表单项;非首屏字段采用延迟初始化,降低初始加载时间,提升首屏渲染速度。
3. 依赖管理
zero-form 以 "字段自治" 为核心,重构字段联动逻辑,解决传统联动 "逻辑分散、难以维护" 的问题:
- 被动联动:每个字段仅关注自身依赖的字段,通过监听依赖字段的变化更新自身状态(如 visible、value),而非主动修改其他字段。例如:字段 B 依赖字段 A 时,字段 B 需定义 dependencies: ['A'],并在 A 变化时触发自身的 onDepsChange 方法更新状态 ------ 彻底消除 "主动修改其他字段" 导致的逻辑混乱。
- 依赖图可视化:基于图论构建字段依赖图,以顶点表示字段、边表示依赖关系,结合拓扑排序确保联动逻辑按正确顺序执行,同时支持可视化展示依赖关系,便于调试与维护。
- 高可测试性:联动逻辑收拢在字段自身,可针对单个字段编写单元测试(如模拟依赖字段变化,验证自身状态是否符合预期),大幅提升测试覆盖率,保障迭代安全性。
目前 zero-form 已经处于内测阶段,敬请期待!