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 模式下的
effects或reactions属性:直接在字段组件上声明reactions,或在createForm({ effects() {...} })中声明effects; - Schema 模式下的
effects或结构化x-reactions属性:把联动逻辑写成 JSON 可序列化的结构化对象; - Schema 模式下的
effects或函数态x-reactions属性:把联动逻辑写成可执行的函数表达式,甚至直接引用注册在上下文作用域中的通用函数。
既然有这么多描述逻辑的方式,就必须先理解effects与reactions的本质定位,才能在具体场景中做出正确选择。
二、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 用于实现副作用隔离的逻辑管理模型。它最大的优势体现在两类场景:
- 字段数量超多时,把逻辑从视图层抽离到统一位置,让视图代码更易维护;
- 批量处理字段:例如 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同文件还提供了一批基于生命周期事件的副作用钩子(如onFieldValueChange、onFieldInputValueChange、onFieldInit、onFieldMount、onFieldUnmount、onFieldValidateStart、onFieldSubmit、onFieldReset、onFieldLoading等),它们由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。
因此,局部逻辑能力必须保留,reactions与effects不是互斥方案,而是互补方案。
五、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 钩子的完整能力(onFieldReact、onFieldValueChange、onFieldInit等)可查阅 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),仅供参考