高频问答与参考回答
1. Formily 和普通表单库的本质差异是什么?
答:Formily 的核心是表单领域模型(@formily/core)+ 响应式运行时(@formily/reactive),而不是一组 UI 组件。它把状态、联动、校验放在模型层,UI 只做渲染映射。
2. 为什么 Formily 在大表单里通常更稳?
答:字段级状态拆分 + 精准依赖收集 + batch 调度,避免整树重渲染。动态数组还做了字段状态转置,防止索引变动导致状态错位。
3. address 和 path 有什么区别?
答:
address:节点绝对路径(保留 VoidField 层级)。path:数据路径(读写 values 的真实路径,通常跳过无数据结构节点)。
4. display: none 和 hidden 的区别?
答:
none:隐藏且剔除提交值(可缓存回写)。hidden:隐藏但保留值。
5. Formily 如何实现“字段未挂载也能 setFieldState”?
答:通过更新队列机制(subscribeUpdate),先记录 patch,字段初始化后回放。
6. x-reactions 的底层是什么?
答:Schema 层把 x-reactions 编译为字段 reactions 函数,最终通过 core 的 setFieldState / setState 生效。
7. 为什么要同时保留主动联动和被动联动?
答:主动联动在一对多场景更直观,被动联动在多依赖计算更简洁。两者适配不同复杂度与可读性需求。
8. Formily 的校验是怎么组织的?
答:@formily/validator 执行规则,反馈写入字段 feedbacks,并区分 validate 结果与 effect 注入结果(Validate* / Effect*)。
9. 为什么 form.validate(pattern) 还能只校验局部?
答:校验内部先 query(pattern),只收集命中字段任务;并受 validatePattern/validateDisplay 进一步过滤。
10. ArrayField 为什么不能只操作数组值?
答:只改值会导致字段状态(错误、脏态、组件 props)与索引错位。Formily 会同步迁移字段图状态。
11. Formily 为什么要有 Graph(getFormGraph/setFormGraph)?
答:用于全量状态快照导入导出(调试、回放、时光旅行、复杂表单状态持久化)。
12. Formily 表达式编译为什么有争议?
答:默认 new Function 能力强但有 CSP 安全边界,严格环境会触发 unsafe-eval。可用 registerCompiler 替换。
13. React 绑定层为什么还能做到性能好?
答:React 组件通过 observer 只订阅 render 实际读取字段属性;模型层变更不会自动扩散到整棵组件树。
14. React StrictMode 下如何避免响应器泄漏?
答:reactive-react 里使用 useCompatFactory + GarbageCollector 做兜底清理。
15. 什么时候选 Formily,什么时候不选?
适合:
- 动态字段多;
- 联动复杂;
- 后端驱动 schema;
- 多团队复用统一建模能力。
不太适合:
- 简单静态表单;
- 团队无法接受较高抽象成本;
- 业务只需轻量受控输入。
16. 如果你来设计 Formily,会最关注什么风险?
答:
- 联动链路可观测性(调试复杂);
- 表达式编译安全边界;
- 动态数组状态一致性;
- 版本升级下协议兼容(polyfill/patch)。
17. 如何回答“Formily 比 react-hook-form 好在哪里”?
答法建议:不做绝对比较,强调定位差异。
Formily 强在领域模型与协议化动态能力;react-hook-form 强在轻量与原生表单范式。选型取决于业务复杂度与团队建模诉求。
18. 一句话总结(终面版)
“Formily 通过响应式领域模型把表单从 UI 状态管理问题升级为可编排的业务建模问题,核心价值在复杂动态场景下的可维护性与可扩展性,而不仅是输入框渲染性能。”
19. 直接用 Formily 时,有没有最佳实践和错误用例对照?
答:有,而且非常值得在团队里沉淀成 checklist。下面这份是我结合官方进阶指南(联动/校验/解构)+ Web 检索到的社区高频坑 + GitHub issue 提炼出来的“对照版”。
| 场景 | 最佳实践(推荐) | 错误用例(避免) | 真实依据 |
|---|---|---|---|
| 动态联动 | 简单一对多用 onFieldValueChange;多依赖计算用 x-reactions.dependencies 或 onFieldReact | 所有联动都塞进一个巨型 x-reactions,依赖关系不可读、难排查 | 官方进阶联动指南(仓库 docs/guide/advanced/linkages.zh-CN.md) |
| 字段未挂载时更新 | 用 form.setFieldState,利用内置“延迟回放”机制 | 手写 setTimeout 轮询等字段出现再更新 | core subscribeUpdate/initFieldUpdate 机制 |
| 初始化阶段改值 | 初始化 early phase 优先 form.setValuesIn 或在字段 mount 后改值 | 在字段第一次 reaction(initialized=false)直接 field.setValue 期待必生效 | #3673 |
| 组件 props 联动 | setComponentProps 传稳定结构,避免把“会变引用的大对象”放入组件 useEffect 依赖 | useEffect(()=>onChange(), [props.xxx]) + 每次 reaction 生成新引用,导致循环触发 | #4169 |
| ArrayField 删除/移动 | 用 ArrayField 自带 push/remove/move 等 API,依赖 onFieldInputValueChange 观察用户输入与结构变化 | 手动改数组值、或错误监听导致多次触发/错位触发 | #2947, #4130, #4323, #4325 |
| 复杂响应链 | 保持 reaction 幂等,必要时拆分 effect;升级时关注 reactive 修复 | 在 reaction 内做大量副作用且无幂等保护,出现链式遗漏或重复触发 | #4324, #4328 |
| Schema 动态排序 | 字段排序优先结构层控制(schema 顺序 / 渲染层策略) | 期望通过 x-reactions 动态改 x-index 实时重排 | #3508 |
| 表达式编译安全 | 强 CSP 项目用 registerCompiler 替换默认编译器;明确表达式来源可信 | 默认开启表达式编译并直接吃不可信输入,或忽略 CSP 报错 | #4338 |
| 可编辑容器类组件 | 先确认字段是否真实渲染并参与 reaction,再设计 setFieldState 更新路径 | 认为所有 UI 容器都等价于常规字段渲染节点 | #3010 |
可落地的团队规范(建议直接抄到项目 README)
- 联动分层:公式型联动走
dependencies,流程型联动走effects。 - 数组操作规范:只用
ArrayFieldAPI,不直接splice(values.xxx)。 - 副作用规范:reaction 函数保持幂等,异步逻辑必须有竞态保护。
- 安全规范:生产环境默认评审
表达式来源;CSP 项目强制自定义 compiler。 - 排障规范:任何“触发次数异常”先查
display/pattern、initialized、dependencies、数组索引迁移。
如果继续追问“你在项目里怎么防这些坑”,就回答:用 lint 规则 + code review checklist + 关键联动单测(数组增删/隐藏显示/reset/submit 组合),而不是只靠个人经验。