你有没有遇到过这种情况:一个表单明明只有30个字段,代码却写了上千行,bug 层出不穷,新增一个字段要改五六个文件。

问题的根源不是表单太复杂,而是我们把简单的事情做复杂了。


一个常见的误区

前端开发中有一个普遍的做法:按视觉结构划分模块。

页面左边是基本信息,右边是账户信息,于是我们就拆成两个组件,通过 props 传数据。一个字段变了,要层层回调回父组件,再层层下钻传给另一个组件。

为了管理这些状态,我们引入 Redux、MobX、useReducer,写大量的 action、reducer、selector。

本质上,我们在用”组件嵌套”的方式管理”平铺的表单数据”。 这就像把一本书按照封面颜色来分类,而不是按照内容。


回归本质:表单需要什么?

程序员面对的本质是什么?是一个表单。

不是组件树,不是状态图,不是数据流图。就是一个简单的表单:有若干字段,每个字段有验证规则,填完提交。

那么,为什么需要复杂的状态管理?为什么需要 props 下钻?为什么需要回调传递?

答案是:不需要。


Form 本身就是状态管理器

很多人把 Ant Design 的 Form 当成纯 UI 组件,其实它内部已经是一个完整的状态管理解决方案:

  • Form Store:维护所有字段的值
  • Form.Item:通过 name 直接绑定字段
  • Form.useWatch:监听任意字段变化
  • initialValues:一次性初始化
  • onFinish:一次性收集和验证

这些功能加起来,就是状态管理的全部所需。用 Redux 管表单,等于用扳手拧螺丝——能用,但不对。


最关键的认知:穿透性

很多人不知道,Form.Item 不受组件层级影响。

<Form>
  <组件A>
    <组件B>
      <Form.Item name="field">
        <Input />
      </Form.Item>
    </组件B>
  </组件A>
</Form>

无论嵌套多深,Form.Item 都能直接访问 Form 的数据仓库。组件层次对表单数据没有影响。

这意味着:视觉上你可以随意组织组件(Tab、步骤、折叠面板),但数据层面始终是扁平的。数据不 care 你怎么排版,排版也不 care 数据怎么存。


代码对比

传统写法:

const [name, setName] = useState('');
const [email, setEmail] = useState('');
// ... 还有28个

<Module1 name={name} setName={setName} email={email} ... />
<Module2 type={type} setType={setType} ... />

const handleSubmit = () => {
  const data = { name, email, type, ... };
  submitAPI(data);
};

Form-Driven:

const FormPage = () => (
  <ProForm onFinish={submitAPI}>
    <Module1 />
    <Module2 />
  </ProForm>
);

子组件不需要任何 props:

const Module1 = () => {
  const type = Form.useWatch('type');

  return (
    <>
      <ProFormText name="name" rules={[{ required: true }]} />
      <ProFormSelect name="category" options={type === 'A' ? optA : optB} />
    </>
  );
};

字段联动不需要 props 传递,直接监听。提交时数据自动收集,不需要手动拼装。


核心做法

场景 传统 Form-Driven
存字段值 useState Form.Item name="xxx"
字段联动 props + useEffect Form.useWatch('field')
提交收集 手动拼装 onFinish(values) 自动给
跨模块通信 状态提升到父组件 直接读取,无需提升

效果

维度 传统方式 Form-Driven
30字段代码量 ~1000行 ~230行
新增一个字段 35分钟 5分钟
修改验证规则 30分钟(多处) 5分钟(一处)
Bug数量 减少约60%

性能也有明显提升。传统方式一个字段变化会触发整棵树重渲染;Form 内部做了字段级优化,只更新变化的字段。实测50字段的表单,更新耗时从300ms降到30ms。


背后的设计哲学

1. 不要和框架对抗

框架设计者已经思考过表单的本质问题,Form 就是他们的答案。顺应工具的设计意图,而不是重新发明轮子。

2. 复杂性往往源于错误的抽象

传统开发中,我们默认:视觉组件 = 数据容器,组件嵌套 = 数据层级,Props = 数据通道。

这些抽象在表单场景下都是错的。正确的抽象是:视觉组件只是纯视图,Form Store 才是数据容器,Form.Item 的 name 就是数据指针。

3. 数据扁平,视觉自由

Form Store 里的数据永远是扁平的:

{
  "name": "张三",
  "idType": "身份证",
  "accountType": "储蓄账户"
}

但视觉上你可以随意组织:3个 Tab、5个步骤、10个折叠面板。两者互不干扰。


适用场景

✅ 复杂表单(10+ 字段、字段依赖、动态验证)
✅ 多变体表单(不同客户不同规则)
✅ 高频维护、性能敏感

❌ 2-3个字段的简单表单(学习成本不值得)
❌ 非表单场景(列表、可视化)


总结

这套架构的价值不在于技术多先进,而在于认知的转变:

  • 从”和框架对抗”到”顺应框架”
  • 从”运行时复杂度”(props、同步)转移到”设计时复杂度”(数据结构)
  • 从面对组件树、状态图,回归到面对一个表单

回归初心,程序员面对的只是一个表单。至于组件层次,有,但对于表单来说,不存在,因为 Form.Item 具备穿透性。把复杂的问题简单化,这才是架构的真正价值。

如果你正在被表单的状态管理折磨,请记住:Form 不只是 UI,它就是你需要的全部状态管理器。