Formily 核心架构深度解析:基于 @formily/reactive 领域模型的响应式表单内核
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
导读
本文围绕 packages/core/docs/guide/architecture.zh-CN.md 展开,剖析 Formily 内核的本质:它并不是一堆表单组件的集合,而是一个构建在 @formily/reactive 之上的响应式领域模型。你将理解 Form/Field/ArrayField/ObjectField/VoidField 五类领域模型如何组织表单状态,Reactions 响应器如何通过依赖追踪消费模型属性,以及生命周期(LifeCycle/Effects)与 UI 框架适配层如何与内核协作。读完本文,你将具备从源码层面理解 Formily 联动、校验、渲染性能机制的能力,并能依据 架构文档 继续深入其他主题。
一、架构全景:为什么内核是一个"领域模型"
Formily 核心架构之所以复杂,是因为它解决的是一个领域级问题而非单点问题。若把表单问题拆解,可以划分为四个方向(见 packages/core/docs/guide/index.md):
- 数据管理:表单值的存储、读写与合并;
- 字段管理:字段的创建、销毁、路径定位与父子关系;
- 校验管理:字段级与表单级校验、错误/警告/成功反馈;
- 联动管理:字段之间、字段与表单之间的状态联动。
Formily 将这四类问题全部抽象为领域模型(Domain Model)来解决,而承载这些领域模型的正是独立于 UI 框架的 @formily/core 包。它刻意与 React/Vue 解耦,从而获得两个直观收益:一是开发者可以摆脱 UI 与逻辑的耦合,提升可维护性;二是使 Formily 具备跨端、跨框架能力(React/React Native/Vue 2/Vue 3,见仓库根目录项目描述)。
正如架构文档所强调的:Formily 内核本质上是 @formily/reactive 领域模型。模型的实例(Form、各类 Field)本身就是可观察(observable)对象;实际消费这些模型的,则主要依赖 @formily/reactive 提供的响应器(Reaction)机制做依赖追踪。响应器可以在其函数体中读取 Form/Field/ArrayField/ObjectField/VoidField 模型的任意属性,被读取的属性发生变化时,响应器会自动重新执行——这就实现了表单层面的响应式编程模型(Reactive Programming Model)。
二、领域模型:五类核心模型与状态组织
从 packages/core/src/models/index.ts 可以看到内核模型家族共 7 个类:
| 模型 | 职责 |
|---|---|
| Form | 表单整体模型:values、initialValues、字段注册表、图、生命周期枢纽 |
| Field | 普通字段(叶子字段),拥有 value/inputValue/validator/feedbacks 等 |
| ArrayField | 数组字段,value 为数组,支持增删排序等数组操作 |
| ObjectField | 对象字段,value 为对象,管理嵌套子字段 |
| VoidField | 无值字段(仅承载布局/展示,不产生表单值) |
| Graph | 表单字段图,支持整表状态快照的导出与还原 |
| Heart | 生命周期(LifeCycle)的发布-订阅枢纽 |
| Query | 按 FormPath 模式查询字段的查询器 |
2.1 Form 模型:内核的"心脏"
packages/core/src/models/Form.ts 中 Form 的构造过程清晰地展示了"模型 + 可观察性"的装配流程(L84-L90):
constructor(props: IFormProps<ValueType>) {
this.initialize(props) // 初始化 id/props/graph/heart 等
this.makeObservable() // 用 @formily/reactive 的 define 声明可观察状态
this.makeReactive() // 用 observe 监听自身变化,驱动表单事件
this.makeValues() // 初始化 values/initialValues
this.onInit() // 触发 ON_FORM_INIT 生命周期
}
其中 makeObservable(L122-L166)揭示了领域模型的注解声明方式:
define(this, {
fields: observable.shallow,
values: observable,
initialValues: observable,
initialized: observable.ref,
validating: observable.ref,
submitting: observable.ref,
loading: observable.ref,
pattern: observable.ref,
display: observable.ref,
valid: observable.computed, // 计算属性
invalid: observable.computed,
errors: observable.computed,
warnings: observable.computed,
setValues: action, // 行为注解:变更会被合并进批量事务
setValuesIn: action,
reset: action,
submit: action,
validate: action,
onMount: batch, // 批量作用域注解
onUnmount: batch,
onInit: batch,
})
而 makeReactive(L168-L179)则通过 observe 在模型上挂了一个深层监听器,任何属性变更都会触发 triggerFormInitialValuesChange / triggerFormValuesChange,进而由 Heart 发布对应生命周期事件——这是"模型变化 → 事件通知"的桥梁。
2.2 字段模型家族:BaseField 的公共能力
Field/ArrayField/ObjectField/VoidField 都继承自 BaseField。BaseField 提供了几个关键的领域能力:
- address/path 定位:
locate()将字段以 address 为键注册进form.fields(L56-L59); - 父子链推导:
parentgetter 沿着 address 向上查找最近的已注册字段(L109-L118); - display 状态继承:字段的 display 会继承父级(如父级
hidden则子级默认继承,除非子级显式声明自己的 display)(L120-L129); - record/index 数据上下文:
record返回当前字段所在对象/数组的上下文数据,index返回数组下标(L61-L87)。
Field 自身的可观察状态(如 value、inputValue、dataSource、validator、feedbacks、active、visited、selfModified 等)同样在构造时通过 makeObservable 声明(见 packages/core/src/models/Field.ts),这保证了对字段属性的读取与写入都能被响应器追踪。
2.3 Graph:整表状态快照
packages/core/src/models/Graph.ts 提供 getGraph()(L22-L29)导出 form + 所有字段状态的完整快照,setGraph()(L31-L59)则按快照类型(FormState/FieldState/ArrayFieldState/ObjectFieldState)重建缺失字段并回填状态。这是 Formily 实现状态可序列化、可恢复(如设计器、DevTools 调试)的基础设施。
三、可观察性底层:@formily/reactive 的实现机制
领域模型的可观察性完全来自 @formily/reactive 包,其公共 API 见 packages/reactive/src/index.ts。架构文档所说"依赖追踪"依赖以下核心机制:
3.1 可观察注解体系
packages/reactive/src/observable.ts 暴露了完整的注解集合:
observable.box = annotations.box
observable.ref = annotations.ref
observable.deep = annotations.observable
observable.shallow = annotations.shallow
observable.computed = annotations.computed
deep(默认):深递归代理整个对象,任何层级属性变更都可被追踪;shallow:仅浅层属性可追踪(如form.fields只需要替换级别的监听);ref:按引用追踪,整体替换属性时才触发(适合 boolean 标记位,如validating);box:装箱一个可变值;computed:声明计算属性,内部读取的其他可观察对象会被注册为依赖。
3.2 依赖追踪与响应器
依赖追踪的核心是全局的 ReactionStack(当前正在执行的响应器栈)。packages/reactive/src/autorun.ts 中 autorun 的执行逻辑是:
- 将当前 reaction 压入
ReactionStack; - 执行 tracker 函数,期间对可观察对象的任何属性读取都会被记录为该 reaction 的依赖;
- 执行完毕弹出栈,之后当任一依赖属性变化时,reaction 会被调度重新执行(先
releaseBindingReactions清理旧依赖,再重新收集)。
同文件中的 reaction(tracker, subscriber)(L102-L153)是带脏检查的变体:tracker 返回一个值,只有当 oldValue !== currentValue(或自定义 equals 判定不相等)时才触发 subscriber,且通过 dirtyCheck 避免无关重放。
batch(packages/reactive/src/batch.ts)提供事务化批量更新:batch 包裹的回调内所有变更统一在结束时派发;batch.scope 用于创建批量作用域;batch.endpoint 则把回调挂到当前批量事务的末尾执行。内核中字段创建(new Field(...) 被 batch(() => {...}) 包裹)、onInit/onMount/onUnmount 都是利用批量机制减少中间态触发的典型场景。
而 observe(packages/reactive/src/observe.ts)提供另一种消费方式:直接监听对象或其子树(deep = true 时通过数据节点的 contains 判断)的任意数据变更,是 Form 模型驱动 ON_FORM_VALUES_CHANGE 等事件的基础。
四、响应器(Reactions)如何消费领域模型
架构文档明确指出:可以在响应器(Reactions)中消费 Form/Field/ArrayField/ObjectField/VoidField 模型中的任意属性,依赖的属性发生变化,响应器就会重复执行。这一承诺在内核中有两层落地:
4.1 字段 props.reactions:声明式响应器
字段的 reactions 配置项由 packages/core/src/shared/internals.ts 的 createReactions 实现:
export const createReactions = (field: GeneralField) => {
const reactions = toArr(field.props.reactions)
field.form.addEffects(field, () => {
reactions.forEach((reaction) => {
if (isFn(reaction)) {
field.disposers.push(
autorun(
batch.scope.bound(() => {
if (field.destroyed) return
reaction(field)
})
)
)
}
})
})
}
要点:每个 reactions 函数都被包装为一次 autorun(外加 batch.scope 批量作用域与 destroyed 防护),因此函数体内读取的字段属性即成为依赖。例如在 packages/core/src/tests/field.spec.ts 的测试用例中,reactions: (field) => {...} 读取其他字段后执行联动赋值——被依赖字段一旦变化,reaction 自动重跑,实现字段联动。这些响应器会在字段销毁时通过 field.disposers 统一释放,避免内存泄漏。
4.2 效果钩子中的响应式 API
在 effects 中,内核还提供了更精细的响应式消费 API(packages/core/src/effects/onFieldEffects.ts):
onFieldReact(pattern, callback)(L108-L119):在字段初始化后为回调建立autorun,回调中读取的字段属性变化时自动重放——适合"始终同步"的联动;onFieldChange(pattern, watches?, callback)(L120-L154):基于reaction精确订阅指定状态(默认['value']),仅当被 watch 的字段状态变化时才触发回调,适合"事件式"联动;onFormReact(callback)(packages/core/src/effects/onFormEffects.ts):表单级 autorun,回调中读取 form 任意属性皆成为依赖。
此外 createReaction(packages/core/src/shared/internals.ts#L1080-L1085)提供了 reaction(tracker, untracked.bound(scheduler)) 的组合模板,供内核内部按需组装"追踪-调度"二元结构。
五、生命周期与 Effects 机制
5.1 Heart:生命周期枢纽
每个 Form 拥有一个 Heart 实例,它是基于 @formily/shared 的 Subscribable 实现的发布-订阅枢纽。publish(type, payload)(L52-L67)会依次:通知内置 lifecycles → 通知外部 outerLifecycles(即 effects 中注册的生命周期)→ 触发订阅者 notify。Form 的 notify()(见 Form.ts#L540-L542)即向 Heart 发布事件,例如 onInit/onMount/onUnmount 分别发布 ON_FORM_INIT/ON_FORM_MOUNT/ON_FORM_UNMOUNT。
5.2 effects 的注册与执行
effects 之所以能以"同步 DSL"方式声明生命周期监听,是因为 packages/core/src/shared/effective.ts 的 runEffects:在 GlobalState.effectStart = true 的同步执行期间,所有 onFormXxx/onFieldXxx 调用都会通过 createEffectHook(L7-L28)收集为 LifeCycle 实例;同步执行结束即返回生命周期数组,交由 Heart 注册。若在异步函数体内使用效果钩子,createEffectHook 会直接抛出 'Effect hooks cannot be used in asynchronous function body'。useEffectForm 则通过 createEffectContext 在 effects 执行期间注入当前 Form 上下文。
5.3 完整生命周期事件
- 表单级(onFormEffects.ts):
onFormInit、onFormMount、onFormUnmount、onFormValuesChange、onFormInitialValuesChange、onFormInputChange、onFormSubmit、onFormSubmitStart/End/Success/Failed、onFormSubmitValidateStart/Success/Failed/End、onFormValidateStart/Success/Failed/End、onFormGraphChange、onFormLoading、onFormReact; - 字段级(onFieldEffects.ts):
onFieldInit、onFieldMount、onFieldUnmount、onFieldValueChange、onFieldInitialValueChange、onFieldInputValueChange、onFieldValidateStart/End/Success/Failed、onFieldValidating、onFieldSubmit及其细化钩子、onFieldReset、onFieldLoading、onFieldReact、onFieldChange。
字段级钩子均支持 FormPath 模式匹配(matchAliasGroup(field.address, field.path)),因此可以精确订阅某个路径下的字段事件。
六、响应式渲染:模型到 UI 的最后一步
领域模型与响应器解决了状态与逻辑,最终还需要把模型变化映射为 UI 更新。以 React 为例,packages/reactive-react/src/observer.ts 的 observer 高阶组件将组件包裹进 useObserver 的依赖追踪作用域:组件渲染期间读取的模型属性会被记录为依赖,属性变化后组件按需重渲染(配合 memo 避免无关注入)。这正是架构文档"依赖追踪 → 按需更新"在渲染层的落地。Vue 端则由 packages/reactive-vue 提供等价能力,而各 UI 库包(packages/react、packages/vue、packages/antd、packages/next、packages/element)通过这套机制把内核领域模型翻译成具体框架组件。
七、总结:从架构文档到源码的印证
回到架构文档的结论——"Formily 内核是一个 @formily/reactive 领域模型,消费模型依赖响应器机制做依赖追踪",从源码可以得到完整印证:
- 模型即可观察对象:
Form.makeObservable/Field.makeObservable通过define注解声明可观察状态(Form.ts#L122-L166); - 响应器即消费者:字段
reactions被包装为autorun(internals.ts#L1062-L1078),onFieldReact/onFormReact同样基于autorun; - 生命周期承载事件:Heart 发布-订阅 + effects 同步收集(Heart.ts#L52-L67、effective.ts#L58-L77);
- 渲染层按需更新:observer/useObserver 依赖追踪(observer.ts)。
开发者只需面向领域模型编写业务逻辑(读取属性、声明 reactions、订阅生命周期),性能与联动细节全部由内核与响应器机制承担。
八、延伸阅读
- 核心包介绍(index) 与 中文版:内核设计理念与收益;
- 字段模型详解 与 表单模型详解:五类模型的完整状态与 API;
- MVVM 架构说明:模型、视图模型与视图的分层关系;
- 数据与值:values/initialValues 的语义;
- 领域模型 API 文档:packages/core/docs/api/models 与 packages/core/docs/api/entry;
- reactive 响应式运行时 API 文档:packages/reactive/docs/api;
- 核心包源码入口:packages/core/src/index.ts 与 reactive 入口 packages/reactive/src/index.ts。
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
更多推荐


所有评论(0)