ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Formily 响应式模型 API:使用 `model()` 快速定义领域模型(@formily/reactive 全解析)

Formily 响应式模型 API:使用 `model()` 快速定义领域模型(@formily/reactive 全解析) 前端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点击查看免费下载model()是 formily/reactive 提供的一键式领域模型定义入口它基于属性的类型自动为对象成员声明合适的响应式行为——getter 属性自动变为 computed计算属性、函数自动变为 action批处理动作、普通属性自动变为 observable深度响应式数据从而让开发者用最少的样板代码构建出具备完整响应式能力的领域模型。本文以 packages/reactive/docs/api/model.zh-CN.md 为主线结合 model.ts 等源码实现深入讲解其自动标注规则、底层调用链、define()手动标注的扩展方式以及它与 autorun/observe 的组合实践。一、model 是什么一句话概括model是 formily/reactive 包中用于快速定义领域模型Domain Model的工厂函数。与手动逐属性配置响应式行为的define不同model会自动扫描传入对象的每个自有属性并按以下规则自动声明响应式行为属性形态自动声明的响应式类型说明getter 属性带get描述符observable.computed计算属性依赖变化时惰性重算函数方法action批处理动作内部多次修改只触发一次通知普通属性observable深度劫持的响应式数据其函数签名见 model.zh-CN.md非常简洁interface modelTarget extends object { (target: Target): Target }输入一个普通对象返回同一个对象原地改造返回引用不变改造后该对象便具有完整的响应式追踪能力可被autorun、observe、tracker等机制订阅。二、从用例看 model 的核心能力文档给出的最小用例展示了model的完整工作流import { model, autorun } from formily/reactive const obs model({ aa: 1, bb: 2, get cc() { return this.aa this.bb }, update(aa, bb) { this.aa aa this.bb bb }, }) autorun(() { console.log(obs.cc) // 首次输出 3 }) obs.aa 3 // 直接改普通属性触发 cc 重算输出 5 obs.update(4, 6) // 经 action 批量修改 aa/bb输出 10这里值得注意的几点实战细节gettercc被自动声明为 computedcc在autorun中被读取其依赖aa、bb一旦变化cc会惰性重算并触发依赖它的autorun回调。因为cc是计算属性update内部连续修改aa和bb时cc只会在最终值确定后重算一次避免了中间态的重复执行。函数update被自动声明为 actionaction 内部的所有响应式修改处于同一个批处理边界内autorun只会收到一次通知而不是aa、bb各触发一次。这正是 define.spec.ts 中define model用例所验证的行为action()调用一次后obs.aa变为 2且整个过程中只发生一次响应式派发。普通属性aa、bb被自动声明为深度 observable嵌套子对象同样会被递归劫持后续新增、修改深层属性都能被追踪。三、model 的实现原理源码级拆解model的实现非常精简完整源码位于 model.tsexport function modelTarget extends object any(target: Target): Target { const annotations Object.keys(target || {}).reduce((buf, key) { const descriptor Object.getOwnPropertyDescriptor(target, key) if (descriptor descriptor.get) { buf[key] observable.computed } else if (isFn(target[key])) { buf[key] action } else { buf[key] observable } return buf }, {}) return define(target, annotations) }整个流程分为两步3.1 第一步遍历属性生成 annotation 映射表model通过Object.keys遍历对象的自有可枚举属性并用Object.getOwnPropertyDescriptor检查每个属性的描述符从而区分三种形态存在get描述符 → 判定为计算属性对应observable.computed属性值类型为函数isFn判定见 checkers.ts→ 判定为方法对应action其余情况 → 判定为普通数据对应observable即深度劫持。这一步产出一个{ [key]: annotation }形式的标注映射表随后整体交给define执行。3.2 第二步调用 define 落地标注define是model的底层执行者同一个文件中的define函数独立的 define.zh-CN.md 文档也对其做了说明export function defineTarget extends object any( target: Target, annotations?: AnnotationsTarget ): Target { if (isObservable(target)) return target // 已是响应式对象则直接返回 if (!isSupportObservable(target)) return target // 不支持的裸对象原样返回 target[ObModelSymbol] target // 打上领域模型标记 buildDataTree(undefined, undefined, target) // 建立数据节点便于路径追踪 for (const key in annotations) { const annotation annotations[key] if (isAnnotation(annotation)) { getObservableMaker(annotation)({ target, key }) // 按标注执行对应 maker } } return target }define的关键逻辑幂等保护isObservable见 externals.ts检查对象是否已被响应化已响应或不受支持的对象如 Moment 对象、带toJS/toJSON的对象见isSupportObservable的黑名单逻辑会原样返回不会重复包装模型标记通过ObModelSymbol定义于 environment.ts标记对象为领域模型后续raw()等 API 可以据此取回原始引用数据树构建buildDataTree见 tree.ts为对象创建DataNode这是contains、路径计算等功能的数据基础标注执行每个 annotation 都通过getObservableMaker见 internals.ts解析出真正的实现函数并以{ target, key }为访问者参数执行完成属性级别的响应式改造。3.3 三种 annotation 在底层的不同表现observable深度劫持把属性值递归代理为 Proxy嵌套的普通对象/数组/集合都会被继续劫持对应 observable.ts 中observable.deep annotations.observable的定义observable.computed计算属性见 computed.ts内部创建一个ComputedReaction通过defineProperty重写目标属性的get/setget时若依赖收集栈中有正在运行的 reaction则绑定依赖脏标记为true时才惰性重算并把结果存入内部 storeset时即用户通过 setter 反向赋值自动包裹在batchStart/batchEnd中action批处理动作见 action.ts通过createBoundaryAnnotation在方法执行前后分别执行batchStart untrackStart与untrackEnd batchEnd从而把方法内部的所有修改收敛为一次通知且执行过程中不收集新依赖untrack语义。四、精确控制从 model 升级到 definemodel是约定优于配置的快速通道但它的自动判定规则是固定的凡是函数一律视为 action、凡是 getter 一律视为 computed。当业务需要更精细的控制时可以使用define手动为每个属性指定 annotation甚至可以混用observable.shallow浅劫持、observable.boxget/set 容器、observable.ref引用劫持和batch模式等方法完整清单见 define.zh-CN.mdimport { define, observable, action, autorun } from formily/reactive class DomainModel { deep { aa: 1 } shallow {} box 0 ref constructor() { define(this, { deep: observable, // 深度劫持 shallow: observable.shallow, // 仅劫持第一层 box: observable.box, // get/set 容器box.get()/box.set() ref: observable.ref, // 引用级劫持 computed: observable.computed, action, }) } get computed() { return this.deep.aa this.box.get() } action(aa, box) { this.deep.aa aa this.box.set(box) } } const model new DomainModel() autorun(() { console.log(model.computed) }) model.action(1, 2) model.action(1, 2) // 重复调用且值未变化不会重复触发响应 model.action(3, 4)define接受的 annotation 与行为对照均可从 define.spec.ts 的用例中验证Annotation行为测试验证observable/observable.deep深度劫持嵌套对象递归代理observable annotation用例observable.shallow仅劫持第一层深层对象原样保留shallow annotation用例observable.box提供get()/set()的容器box annotation用例observable.ref只劫持属性引用本身ref annotation用例observable.computed计算属性依赖变化惰性重算computed annotation用例action/batch方法体成为批处理边界action annotation用例另外注意define对非对象输入数字、字符串、函数、数组会原样返回不做处理这一点在define.spec.ts的unexpect target用例中有明确覆盖见 define.spec.ts。五、与 autorun / observe 组合的响应式闭环model返回的对象可以直接接入 formily/reactive 的所有订阅 API形成模型变更 → 视图/副作用更新的闭环。核心导出均来自 index.ts常用组合包括import { model, autorun, observe } from formily/reactive const store model({ count: 0, get double() { return this.count * 2 }, increment() { this.count }, }) // 1) autorun任意依赖变化时重新执行 autorun(() { console.log(count , store.count, double , store.double) }) // 2) observe观察对象整体或某个 key 的变化 observe(store, (event) { console.log(changed key:, event.key, oldValue:, event.oldValue, value:, event.value) }) store.increment() // 触发 autorun 与 observe 回调其中autorun是立即执行一次 依赖变化重跑的追踪器适合驱动渲染或副作用observe更接近事件监听回调携带{ type, key, value, oldValue }等变更信息DataChange结构见 tree.ts适合做持久化、日志、联动等副作用由于increment是 actioncount只会触发一次通知即使一次方法内修改多个字段也只会派发一次。六、注意事项与边界对象必须可响应化model内部会调用isSupportObservable见 externals.ts做兼容性检查。Moment 对象、JSON Schema 对象、带toJS/toJSON/$$typeof的库对象、已被markRaw标记的对象都不会被劫持model会原样返回避免破坏第三方库内部机制。重复调用安全对已是响应式对象再次调用model/define会因isObservable检查直接返回不会重复代理。getter 必须有 setter 才能反向赋值model自动声明的 computed 支持 setter见 computed.ts但只有原对象定义了set时才有效未定义 setter 的只读 getter 只能作为派生值读取。this 绑定action 在批处理执行时会用bound包装方法createBoundaryAnnotation中的boundary.bound(target[key], target)见 internals.ts方法内的this会被绑定到模型对象本身因此文档用例中的this.aa aa可以安全使用。包版本与适用环境本文描述的行为基于仓库当前 formily/reactive 2.3.7 版本的实现该包支持 npm 直装npm install formily/reactive构建产物包含 CJS/ESM/UMD 多种格式可同时用于浏览器与 Node 环境对应package.json中的main/module/umd:main字段。七、小结model用一行调用完成了普通对象 → 响应式领域模型的转换getter 自动变 computed、函数自动变 action、普通属性自动变 observable配合 autorun/observe 即可构建出高性能、可预测的响应式数据层。当自动规则无法满足需求时define提供了逐属性手动的完整控制面。两者的核心实现model.ts、define、computed.ts、action.ts与测试用例define.spec.ts均可在此仓库中继续深入研读是理解 Formily 整套响应式内核的最佳切入点。赞分享前端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点击查看免费下载相关推荐Formily Reactive 的 define 领域模型定义深入解析注解式响应式建模Formily Reactive 的 define 领域模型定义深入解析注解式响应式建模 导读 define 是 formily/reactive 包提供的前端UI组件formily/reactive model API 深度解析用声明式语法自动构建响应式领域模型formily/reactive model API 深度解析用声明式语法自动构建响应式领域模型 model 是 formily/reactive htt前端UI组件Formily 核心架构解析基于 formily/reactive 响应式领域模型的设计原理Formily 核心架构解析基于 formily/reactive 响应式领域模型的设计原理 导读 本文围绕 packages/core/docs/guid前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表