• 前端
  • UI组件

【免费下载链接】formily

📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3

项目地址: https://gitcode.com/gh_mirrors/fo/formily
点击查看 免费下载

导读

本文围绕 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):

  1. 数据管理:表单值的存储、读写与合并;
  2. 字段管理:字段的创建、销毁、路径定位与父子关系;
  3. 校验管理:字段级与表单级校验、错误/警告/成功反馈;
  4. 联动管理:字段之间、字段与表单之间的状态联动。

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);
  • 父子链推导:parent getter 沿着 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 的执行逻辑是:

  1. 将当前 reaction 压入 ReactionStack;
  2. 执行 tracker 函数,期间对可观察对象的任何属性读取都会被记录为该 reaction 的依赖;
  3. 执行完毕弹出栈,之后当任一依赖属性变化时,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 领域模型,消费模型依赖响应器机制做依赖追踪",从源码可以得到完整印证:

  1. 模型即可观察对象:Form.makeObservable/Field.makeObservable 通过 define 注解声明可观察状态(Form.ts#L122-L166);
  2. 响应器即消费者:字段 reactions 被包装为 autorun(internals.ts#L1062-L1078),onFieldReact/onFormReact 同样基于 autorun;
  3. 生命周期承载事件:Heart 发布-订阅 + effects 同步收集(Heart.ts#L52-L67、effective.ts#L58-L77);
  4. 渲染层按需更新:observer/useObserver 依赖追踪(observer.ts)。

开发者只需面向领域模型编写业务逻辑(读取属性、声明 reactions、订阅生命周期),性能与联动细节全部由内核与响应器机制承担。

八、延伸阅读

  • 前端
  • UI组件

【免费下载链接】formily

📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3

项目地址: https://gitcode.com/gh_mirrors/fo/formily
点击查看 免费下载

更多推荐