Formily 2.x 业务逻辑管理指南:effects 与 reactions 的定位、选择与最佳实践
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/formilyFormily 2.x 提供了多种描述表单业务逻辑的方式——字段局部的reactions、Schema 协议中的x-reactions结构化与函数态两种写法以及从 1.x 继承并重构的effects。本文以「管理业务逻辑」为主题系统讲解这几种逻辑承载形态各自的定位、适用场景、优先级与底层实现原理并结合源码给出可直接落地的配置示例帮助你为不同复杂度的表单选择正确的逻辑管理策略。一、Formily 2.x 描述逻辑的三种方式在纯 JSX源码模式与 SchemaJSON Schema模式下Formily 2.x 描述逻辑的能力可以总结为以下三条路径纯 JSX 模式下的effects或reactions属性直接在字段组件上声明reactions或在createForm({ effects() {...} })中声明effectsSchema 模式下的effects或结构化x-reactions属性把联动逻辑写成 JSON 可序列化的结构化对象Schema 模式下的effects或函数态x-reactions属性把联动逻辑写成可执行的函数表达式甚至直接引用注册在上下文作用域中的通用函数。既然有这么多描述逻辑的方式就必须先理解effects与reactions的本质定位才能在具体场景中做出正确选择。二、reactions字段属性上的局部响应器reactions是挂在具体字段属性上的响应器。它的核心特点是函数内部依赖了哪些响应式数据当这些数据变化时函数就会重复执行。因此它简单直接、容易理解非常契合字段级的小逻辑/* eslint-disable */ Field nameA reactions{(field) { /**具体逻辑实现**/ }} /从源码层面看reactions的响应式执行机制位于 packages/core/src/shared/internals.ts 的createReactionsexport 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.tsexport 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 的getUserReactionsconst 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 方案通过setTimeoutfield.loading模拟异步Schema 方案则通过scope.asyncVisible函数配合x-reactions.fulfill.run执行。七、业务逻辑管理的优先级总结综合以上分析Formily 2.x 管理业务逻辑的方式有以下优先级建议纯源码模式字段数量庞大、逻辑复杂优先选择在effects中定义逻辑字段数量少、逻辑简单优先选择在reactions中定义逻辑。Schema 模式不存在异步逻辑优先选择结构化x-reactions定义逻辑存在异步逻辑或大量计算优先选择函数态x-reactions定义逻辑。八、相关文档与进一步阅读联动逻辑的完整实战示例一对一、一对多、依赖、链式、循环、自身、异步联动均有 Effects 与 SchemaReactions 双版本实现docs/guide/advanced/linkages.mdformily/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),仅供参考