Ant Design Form 的dependencies依赖字段机制:校验联动与精准局部渲染实战
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design
Form.Item的dependencies属性用于声明字段之间的依赖关系:当一个字段所依赖的关联字段值发生变化时,该字段会自动触发重新校验与状态更新。本文以 form-dependencies 演示(源码见 form-dependencies.tsx)为骨架,结合 antd 源码中Form.Item的实现约束、完整 API 文档与 FAQ,讲解依赖字段的定义方式、校验联动原理、与shouldUpdate/useWatch的边界,以及Form.List、setFieldsValue等易踩坑场景。
dependencies 是什么:字段间的响应式关联
antd Form 默认采用增量更新策略,只有被修改的字段及其相关组件才会触发重渲染,以此获得性能优化。dependencies就是在这种增量模型下声明"相关"关系的官方属性。
官方文档对它的定位非常明确(见 API 文档 dependencies 小节):
当字段间存在依赖关系时使用。如果一个字段设置了
dependencies属性,那么它所依赖的字段更新时,该字段将自动触发更新与校验。
其属性签名如下(同样记录于 Form.Item API 表):
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
dependencies | 设置依赖字段 | NamePath[] | - |
其中NamePath既可以是字符串(如'password'),也可以是嵌套路径数组(如['user', 'name']),用来指向表单 store 中任意深度的字段。
一个最常见的实战场景就是注册表单里的"密码 / 确认密码"校验。下面先从演示 Demo 入手,看它究竟怎么用。
Demo 逐行拆解:密码一致性校验
仓库中的演示文档 form-dependencies.md 只给出两句说明(中英双语),真正的技术含量在其配套源码 form-dependencies.tsx 中。它在一个表单里演示了dependencies的两种典型用法。
场景一:字段级校验联动(密码 + 确认密码)
const [form] = Form.useForm(); <Form form={form} name="dependenciesDemo" autoComplete="off" style={{ maxWidth: 600 }} layout="vertical" > <Form.Item label="Password" name="password" rules={[{ required: true }]}> <Input /> </Form.Item> {/* Field */} <Form.Item label="Confirm Password" name="password2" dependencies={['password']} rules={[ { required: true }, ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject( new Error('The new password that you entered do not match!'), ); }, }), ]} > <Input /> </Form.Item> </Form>这里的关键点有三个:
- 声明依赖:
password2通过dependencies={['password']}声明自己依赖password字段; - 校验规则访问全局值:校验函数利用规则回调中解构出的
getFieldValue('password')读取当前表单 store 中password的最新值,与自己value比较,不一致则Promise.reject抛出错误,一致则Promise.resolve通过; - 触发时机:由于声明了依赖,当用户修改
password时,antd 会自动重新执行password2的校验——而不是等到用户再动一次password2才校验。
如果没有dependencies,那么用户先输入密码、再填写确认密码,两者是一致的;随后用户回头把password改掉,password2却不会主动重新校验,页面上会残留"校验通过"的错误状态。这正是 Demo 顶部Alert提示"Try modifyPassword2and then modifyPassword"想让你亲身体验的链路:先填好两个字段,再回头修改第一个,观察第二个是否被同步重新校验。
从实现上看,这属于 Form.Item 级联动的"简单模式":字段有自己的
name,dependencies只是额外的重校验触发器。规则对象中({ getFieldValue }) => ({ validator })的写法是 rc-field-form 的"关联校验"惯用法,注意它返回的是对象而非直接执行。
场景二:renderProps 精准局部渲染
Demo 的第三段展示了dependencies的另一种能力——让无name的Form.Item仅在依赖字段变化时重渲染:
{/* Render Props */} <Form.Item noStyle dependencies={['password2']}> {() => ( <Typography> <p>Only Update when <code>password2</code> updated:</p> <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre> </Typography> )} </Form.Item>这个Form.Item没有name,只配置了noStyle(不渲染任何默认 DOM 结构与错误信息)和dependencies={['password2']}。它的子节点是 render function,渲染逻辑是输出整个表单 store 的 JSON。
在 rc-field-form 的订阅模型下,带 renderProps 的 Form.Item 需要显式声明"什么时候该重渲染":声明了dependencies后,它只订阅password2字段的变化,因此只有password2被修改时这段 JSON 才会刷新;你改password,这段区域不会重渲染。这在表单中用来渲染与某个字段强相关的辅助信息、或者按依赖字段值切换子字段选项时非常有用,也是它与shouldUpdate(表单任意变化都重渲染)的核心区别。
结合源码理解使用约束
dependencies的正确用法在 FormItem 实现 中有严格的开发期约束。开发模式下,InternalFormItem会根据 props 组合输出 warning:
shouldUpdate与dependencies互斥:见 index.tsx:
warning( !(shouldUpdate && dependencies), '`shouldUpdate` and `dependencies` shouldn't be used together. See https://u.ant.design/form-deps.', );对应文档 FAQ 中的说明:"dependencies不应和shouldUpdate一起使用,因为这可能带来更新逻辑的混乱。"两者都在改变 Form.Item 的更新策略,语义却不同:dependencies是精确的依赖订阅(只监听声明的字段),shouldUpdate是全量监听(任一字段变化都重渲染)。
- renderProps 必须搭配
shouldUpdate或dependencies:见 index.tsx:
warning( !!(shouldUpdate || dependencies), 'A `Form.Item` with a render function must have either `shouldUpdate` or `dependencies`.', );因为 Form 默认增量更新,纯函数式子节点没有"更新触发源",所以必须有其中一个属性来声明订阅范围,否则函数永远不会被重新调用。
- 带
dependencies但无name且非 renderProps 时会告警:见 index.tsx:
warning( dependencies && !isRenderProps && !hasName ? ... : ..., 'Must set `name` or use a render function when `dependencies` is set.', );也就是说,dependencies的两种合法姿势是:① 配合name做字段级校验联动;② 配合 render function(子节点为函数)做精准订阅渲染。这两类用法在 index.test.tsx 中都有对应的 warning 测试用例(如"shouldUpdateshouldn't work withdependencies"、"Must setnameor use a render function whendependenciesis set"),可作为行为契约参考。
更完整的实战:注册页密码确认(register Demo)
form-dependencies.tsx 是聚焦单一机制的迷你示例;仓库中的 register.tsx 则在完整注册表单里呈现了同一模式在生产形态下的写法,包含hasFeedback反馈图标:
<Form.Item name="password" label="Password" rules={[{ required: true, message: 'Please input your password!' }]} hasFeedback > <Input.Password /> </Form.Item> <Form.Item name="confirm" label="Confirm Password" dependencies={['password']} hasFeedback rules={[ { required: true, message: 'Please confirm your password!' }, ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject( new Error('The new password that you entered do not match!'), ); }, }), ]} > <Input.Password /> </Form.Item>与 Demo 相比,这个例子的差异点值得注意:使用Input.Password隐藏明文、用hasFeedback让校验状态(成功/错误)有可视化图标反馈、为required提供了自定义message。当用户修改password后,confirm字段的校验会自动重跑,若与新密码不一致会立刻在confirm输入框下方出现错误提示并清除通过状态——这就是dependencies在真实业务中最典型的落地形态。
依赖的路径边界与易踩坑场景
依赖 Form.List 下的字段:路径必须完整
在 FAQ"为什么 Form.Item 的dependencies对 Form.List 下的字段没有效果?"(见 index.zh-CN.md FAQ)中,官方给出了解释:Form.List 下的字段天然包裹在 List 自身的name之下,因此依赖路径也要带上 List 前缀。
<Form.List name="users"> {(fields) => fields.map((field) => ( <React.Fragment key={field.key}> <Form.Item name={[field.name, 'name']} {...someRest1} /> <Form.Item name={[field.name, 'age']} {...someRest1} /> </React.Fragment> )) } </Form.List>此时若要表达对第一行name的依赖,写法是dependencies={[['users', 0, 'name']]}(嵌套路径);用字符串'users'或省略 List 前缀都不会命中目标字段。
dependencies不响应setFieldsValue
FAQ"为什么dependencies不能响应setFieldsValue触发的更新?"(index.zh-CN.md FAQ)同样是高频疑问。官方答复要点:
dependencies主要用于字段间的校验联动,依赖字段由用户交互触发更新时,会重新触发当前字段的更新与校验。如果需要根据setFieldsValue后的值变化来渲染额外内容或切换字段选项,请使用shouldUpdate或useWatch。
原因在于 antd Form 的 change 事件只在用户交互时触发(设计上是为了避免在 change 回调中调用setFieldsValue形成死循环),程序化的setFieldsValue不会产生"交互式更新"链路。与之配套的另一个 FAQ("setFieldsValue不会触发onFieldsChange和onValuesChange?")把这一设计初衷讲得更透:需要消费setFieldsValue带来的值变化时,请改用useWatch或 renderProps 方案。
因此选择依据可归纳为:
| 需求 | 推荐方案 |
|---|---|
| 字段 A 变化后重新校验字段 B(用户交互触发) | dependencies |
| 字段 A 变化后按值渲染 B 的选项/辅助 UI | dependencies(renderProps)或useWatch |
| 表单任意变化都重渲染某区域 | shouldUpdate(true 或对比函数) |
监听setFieldsValue程序化赋值后的值 | useWatch(或shouldUpdate) |
useWatch属于 Hooks 层面的字段订阅,比dependencies更灵活地适用于"读取字段值派生 UI"的场景,API 签名为Form.useWatch(namePath, formInstance?);而dependencies的优势在于它与字段校验生命周期深度绑定,能同时触发"重新校验"。
小结
dependencies是 antd Form 中处理字段间依赖的核心声明式 API,它回答的问题是"当我改了一个字段,哪些字段需要被重新校验、哪些区域需要重新渲染"。掌握三个要点即可在生产中游刃有余:
- 配
name:声明依赖后,被依赖字段变化会自动触发本字段重新校验,典型场景是密码/确认密码一致性校验(演示见 form-dependencies.tsx 与 register.tsx); - 配 renderProps:无
name的 Form.Item 以函数为子节点并声明dependencies,可把重渲染精确限定在依赖字段变化时; - 认清边界:
dependencies与shouldUpdate不可混用、不可省略name或 renderProps、嵌套字段(Form.List)必须写全路径、且不响应setFieldsValue的程序化赋值——后两类场景请切换到useWatch/shouldUpdate。这些约束在 FormItem 源码 的 warning 逻辑和官方 FAQ 中均有明确依据。
理解并正确组合dependencies、shouldUpdate、useWatch,就能在 antd 增量更新模型下既拿到联动校验的可靠性,又保住大表单的性能。
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考