Formily 2.x 业务逻辑管理指南:effects 与 reactions 的定位、选择与最佳实践
2026/9/23 3:07:17 网站建设 项目流程

Formily 2.x 业务逻辑管理指南:effects 与 reactions 的定位、选择与最佳实践

【免费下载链接】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

Formily 2.x 提供了多种描述表单业务逻辑的方式——字段局部的reactions、Schema 协议中的x-reactions(结构化与函数态两种写法),以及从 1.x 继承并重构的effects。本文以「管理业务逻辑」为主题,系统讲解这几种逻辑承载形态各自的定位、适用场景、优先级与底层实现原理,并结合源码给出可直接落地的配置示例,帮助你为不同复杂度的表单选择正确的逻辑管理策略。

一、Formily 2.x 描述逻辑的三种方式

在纯 JSX(源码)模式与 Schema(JSON Schema)模式下,Formily 2.x 描述逻辑的能力可以总结为以下三条路径:

  • 纯 JSX 模式下的effectsreactions属性:直接在字段组件上声明reactions,或在createForm({ effects() {...} })中声明effects
  • Schema 模式下的effects或结构化x-reactions属性:把联动逻辑写成 JSON 可序列化的结构化对象;
  • Schema 模式下的effects或函数态x-reactions属性:把联动逻辑写成可执行的函数表达式,甚至直接引用注册在上下文作用域中的通用函数。

既然有这么多描述逻辑的方式,就必须先理解effectsreactions的本质定位,才能在具体场景中做出正确选择。

二、reactions:字段属性上的局部响应器

reactions是挂在具体字段属性上的响应器。它的核心特点是:函数内部依赖了哪些响应式数据,当这些数据变化时,函数就会重复执行。因此它简单直接、容易理解,非常契合字段级的小逻辑:

/* eslint-disable */ <Field name="A" reactions={(field) => { /**具体逻辑实现**/ }} />

从源码层面看,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会被统一收敛为数组(toArr),且支持函数与结构化对象两种形态;
  • 每个函数态reaction都被包进一个autorun响应式作用域,这正是「基于函数内依赖的数据变化重复执行」的底层来源;
  • 执行被包裹在batch.scope.bound中,避免频繁触发多余的重渲染;字段销毁后(field.destroyed)不再执行,避免泄漏。

三、effects:副作用隔离与批量处理逻辑模型

effects是 Formily 用于实现副作用隔离的逻辑管理模型。它最大的优势体现在两类场景:

  1. 字段数量超多时,把逻辑从视图层抽离到统一位置,让视图代码更易维护;
  2. 批量处理字段:例如 A、B、C 三个字段声明了完全相同的x-reactions逻辑,在effects中只需要写一次:
onFieldReact('*(A,B,C)', (field) => { //...逻辑 })

其中*(A,B,C)是 FormPath 通配匹配语法,可一次命中多个字段。

使用effects的另一个好处是可复用逻辑插件化:可以把一系列通用逻辑封装成可插拔的插件,同时还能做全局监控之类的事情(例如统一监听所有字段的值变化、校验状态等)。

effects 的源码实现

onFieldReact为例,其实现位于 packages/core/src/effects/onFieldEffects.ts:

export function onFieldReact( pattern: FormPathPattern, callback?: (field: GeneralField, form: Form) => void ) { onFieldInit(pattern, (field, form) => { field.disposers.push( autorun(() => { if (isFn(callback)) callback(field, form) }) ) }) }
  • onFieldReact内部先调用onFieldInit完成字段初始化(若字段尚未挂载,则等待其出现),再把回调包进autorun并挂到field.disposers上;
  • 因此onFieldReact响应式的——回调中访问的字段状态发生变化时,回调会自动重跑,这与局部reactions的响应式语义完全一致,只是把作用范围从「单个字段」扩展到了「匹配 pattern 的一批字段」。

onFieldReact同文件还提供了一批基于生命周期事件的副作用钩子(如onFieldValueChangeonFieldInputValueChangeonFieldInitonFieldMountonFieldUnmountonFieldValidateStartonFieldSubmitonFieldResetonFieldLoading等),它们由createFieldEffect统一生成,每个钩子都会做FormPath.parse(pattern).matchAliasGroup(field.address, field.path)的路径匹配,并包在batch中执行回调(见同文件 L13-L32)。相关生命周期类型定义在 packages/core/src/types.ts 中,reactions支持FieldReaction[] | FieldReaction

四、是否还需要局部定义逻辑?

并不是。选择effects还是reactions,本质是维护成本与表达成本的权衡

  • 字段数量很多、逻辑复杂:视图层满屏reactions会很难阅读与维护,把逻辑抽离到effects统一管理是更好的策略;
  • 字段数量很少、逻辑简单:直接在字段属性上写reactions清晰明了,没有必要绕道effects

因此,局部逻辑能力必须保留reactionseffects不是互斥方案,而是互补方案。

五、Schema 模式:结构化 x-reactions

由于 JSON Schema 可以被配置化系统消费(例如低代码平台的配置界面),我们需要在配置界面上对某个具体字段做逻辑配置,因此必须支持结构化描述逻辑的能力。结构化x-reactions的典型写法如下:

{ "x-reactions": { "dependencies": ["aa"], "fulfill": { "state": { "visible": "{{$deps[0] == '123'}}" } } } }

关键字段说明:

  • dependencies:声明本字段依赖的其他字段路径,编译期会收集为$deps数组供表达式引用;
  • fulfill.state:满足条件(默认恒为 true)时对目标字段状态的批量赋值,赋值表达式支持{{...}}模板插值;
  • $deps[0]:取dependencies中第一个依赖字段的当前值;
  • 该示例表达的是:当依赖字段aa的值等于字符串'123'时,本字段visible置为true

这类结构化写法可以解决大部分配置场景的联动需求,因为它纯粹由数据驱动、可序列化、可持久化到后端。

结构化 x-reactions 的编译链路

结构化x-reactions的编译入口位于 packages/json-schema/src/transformer.ts 的getUserReactions

const reactions: SchemaReaction[] = toArr(schema['x-reactions']) return reactions.map((unCompiled) => { return (field: Field) => { const baseScope = getBaseScope(field, options) const reaction = shallowCompile(unCompiled, baseScope) if (!reaction) return if (isFn(reaction)) { return reaction(field, baseScope) } const { when, fulfill, otherwise, target, effects } = reaction const run = () => { const $deps = getDependencies(field, reaction.dependencies) ... const request = condition ? fulfill : otherwise ... } if (target) { reaction.effects = effects?.length ? effects : DefaultFieldEffects } if (reaction.effects) { autorun.memo(() => { untracked(() => { each(reaction.effects, (type) => { if (FieldEffects[type]) { FieldEffectstype } }) }) }, []) } else { run() } } })

从中可以读出几个重要事实:

  • x-reactions会被toArr收敛成数组,每个字段可以同时挂多条 reaction,因此也支持数组形态的x-reactions
  • 如果编译结果是函数,则直接以函数态调用;否则解构出when / fulfill / otherwise / target / effects结构化字段;
  • dependencies通过getDependencies收集为$deps注入表达式作用域;
  • 未显式声明target时作用于字段自身,声明了target则默认绑定DefaultFieldEffects生效时机(即在字段初始化和值变化时执行联动);
  • 最终,Schema 的x-reactions会在 transformFieldProps 中被转换为字段的reactions属性(基础 reaction + 用户 reactions 拼接),从而与纯 JSX 模式共用同一套字段响应机制——这正是「Schema 协议最终编译为字段属性」的架构设计。

Schema 表达式作用域

结构化表达式中可引用的作用域变量(在 transformer.ts 中注入)主要包括:

  • $self:当前字段;
  • $form:表单实例;
  • $deps/$dependencies:依赖字段值数组;
  • $target:目标字段(target指定);
  • $values:表单值;
  • $props$observable$effect$memo$index以及options.scope中注册的自定义变量(如customFunction)。

六、Schema 模式:函数态 x-reactions

当联动过程存在异步逻辑非常复杂、或者存在大量数据处理时,结构化写法难以承载,此时只能开放函数态描述的能力:

{ "x-reactions": "{{(field)=>{/**具体逻辑实现**/}}}" }

这种写法已经很接近低代码配置的形态——把一段可执行函数直接以字符串模板的形式放进 Schema。

更进一步,还可以在上下文作用域中注册一系列通用逻辑函数,然后在 Schema 中按名称引用:

{ "x-reactions": "{{customFunction}}" }

其中customFunction通过createSchemaField({ scope: { customFunction } })(React 侧)或createForm({ effects })配合作用域注入。这种方式让配置系统可以复用沉淀下来的通用逻辑函数,实现「低代码 + 可编程」的结合。异步场景的完整示例可以参考联动逻辑文档中的 异步联动 一节(Effects 方案通过setTimeout+field.loading模拟异步;Schema 方案则通过scope.asyncVisible函数配合x-reactions.fulfill.run执行)。

七、业务逻辑管理的优先级总结

综合以上分析,Formily 2.x 管理业务逻辑的方式有以下优先级建议:

  • 纯源码模式
    • 字段数量庞大、逻辑复杂:优先选择在effects中定义逻辑;
    • 字段数量少、逻辑简单:优先选择在reactions中定义逻辑。
  • Schema 模式
    • 不存在异步逻辑:优先选择结构化x-reactions定义逻辑;
    • 存在异步逻辑或大量计算:优先选择函数态x-reactions定义逻辑。

八、相关文档与进一步阅读

  • 联动逻辑的完整实战示例(一对一、一对多、依赖、链式、循环、自身、异步联动,均有 Effects 与 SchemaReactions 双版本实现):docs/guide/advanced/linkages.md
  • @formily/core中 effects 钩子的完整能力(onFieldReactonFieldValueChangeonFieldInit等)可查阅 packages/core/src/effects/onFieldEffects.ts,其生命周期类型定义在 packages/core/src/types.ts;
  • 响应式执行机制(autorun/reaction)在 packages/core/src/shared/internals.ts 与 packages/reactive 包中实现;
  • Schema 编译与x-reactions转换链路见 packages/json-schema/src/transformer.ts。

【免费下载链接】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

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询