ARTICLE DETAIL

资讯详情

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

MobX-State-Tree 设计哲学:可变数据的简洁与不可变数据的可追溯性如何兼得

MobX-State-Tree 设计哲学:可变数据的简洁与不可变数据的可追溯性如何兼得 状态管理前端【免费下载链接】mobx-state-treeFull-featured reactive state management without the boilerplate项目地址https://gitcode.com/gh_mirrors/mo/mobx-state-tree点击查看免费下载导读MobX-State-TreeMST是一个开箱即用的响应式状态管理容器它试图同时吸收可变数据mutable与不可变数据immutable两大阵营的最佳特性既保留可变对象直观的读写体验又提供快照snapshot、JSON 补丁patch与可重放动作replayable action带来的可追溯性并在底层由 MobX 驱动获得响应式性能。本文以仓库中 docs/intro/philosophy.md 为骨架结合 src/ 源码与 docs/concepts/ 系列文档深入讲解living tree活树模型、受保护的可变状态、快照/补丁/动作三大分发机制、活性保证liveliness以及React, but for data的类比读完你将理解 MST 的核心设计取舍并能在实际项目中正确使用types.model、actions、onSnapshot、onPatch与getSnapshot等核心 API。一、核心命题MST 在状态管理光谱上站在哪里mobx-state-tree以下简称 MST在 docs/intro/philosophy.md 中被明确定义为一个状态容器它结合了可变数据的简单性与易用性、不可变数据的可追溯性与可观察数据的响应性与性能。换句话说MST 试图把两种主流状态管理范式的最佳部分拼合起来不可变immutable路线的优势事务性transactionality、可追溯性traceability、可组合性composition可变mutable路线的优势可发现性discoverability、就近共置co-location、封装性encapsulation。与 MobX 本身不同MST 对数据应当如何被结构化、如何被更新持非常强主张opinionated的态度——它不提供无限自由而是通过一套内置规则让很多常见问题序列化、调试、协作、规范化、类型安全开箱即用地被解决。仓库中的 docs/intro/welcome.md 有一段形象的类比MobX 是状态管理引擎而 MobX-State-Tree 是一辆豪华轿车。MST 在 MobX 之上附加了结构、工具与约束让你能更快到达目的地它也要求你了解约束才能发挥全部价值。二、Living Tree树的形状类型与状态数据MST 的中央概念是活树living tree。一棵树由可变的、但受到严格保护的对象组成这些对象上携带运行时类型信息。也就是说树中的每一个节点同时具备shape形状——由类型信息描述例如Todo拥有title: string与done: booleanstate状态——当前持有的数据。从这棵活树上MST自动生成不可变的、结构共享的structurally shared快照。开发者不需要手写序列化/反序列化代码类型信息让快照 ↔ 活树的互转零成本完成。最小可运行示例来自 philosophy 文档import { types, onSnapshot } from mobx-state-tree const Todo types .model(Todo, { title: types.string, done: false }) .actions((self) ({ toggle() { self.done !self.done } })) const Store types.model(Store, { todos: types.array(Todo) }) // create an instance from a snapshot const store Store.create({ todos: [ { title: Get coffee } ] }) // listen to new snapshots onSnapshot(store, (snapshot) { console.dir(snapshot) }) // invoke action that modifies the tree store.todos[0].toggle() // prints: { todos: [{ title: Get coffee, done: true }]}注意几个细节done: false没有显式声明类型——MST 会自动推断为boolean并附带默认值falseStore.create({...})接收的是一个快照纯对象而不是实例快照到活树的转换自动发生修改树只能通过 action这里即toggle()直接赋值会抛异常onSnapshot会在每次事务结束后推送新的快照控制台输出{ todos: [{ title: Get coffee, done: true }]}。仓库里与之对应的真实用法可见tests/core/model.test.ts、tests/core/action.test.ts 等测试文件。三、快照Snapshots不可变、可传输、可恢复快照是树在某个时间点的不可变序列化结果——它只是纯对象plain object不含任何类型信息也剥离了所有动作因此天然适合网络传输与持久化。MST 在后台始终为每个节点维护一份快照并采用结构共享所以请求快照非常廉价。快照三件套 API方法作用getSnapshot(model, applyPostProcess true)返回当前状态的快照onSnapshot(model, callback)注册监听器每当一个 MobX 事务结束产生新快照时回调一次applySnapshot(model, snapshot)用快照更新模型及其所有后代的完整状态对应源码实现在 src/core/mst-operations.tsonSnapshot与 src/core/mst-operations.tsapplySnapshot、src/core/mst-operations.tsgetSnapshot。其中onSnapshot最终落在节点层的ObjectNode.onSnapshot见 src/core/node/object-node.ts其内部通过 MobXreaction订阅computed snapshot保证每次事务只推送一次见 src/core/node/object-node.ts。快照的特性与等价写法快照不可变、可传输、可用来更新或恢复模型快照在需要时会自动转换为模型实例因此下面两句是等价的store.todos.push(Todo.create({ title: test })) store.todos.push({ title: test }) // 快照自动被转成 Todo 实例当应用快照时MST 会尽可能对树节点做协调reconcile复用类型相同且位置对应的旧节点而非无脑重建这是后面React, but for data类比的重要一环。快照与时间旅行由于快照⇄活树可零成本互转时间旅行time travelling开箱即用——保存一份历史快照、再用applySnapshot恢复即可。也正因如此**HMR热模块替换**的支持变得微不足道模块热更新时只需取出当前快照、重建 store再应用回快照即可无缝保留状态。四、运行时类型校验与类型系统MST 的类型信息被设计为同时在设计期与运行期发挥作用运行期任何赋值都会经过类型检查。若数据不合法MST 会抛出带路径与期望结构的错误信息例如[mobx-state-tree] Value {todos:[{turtle:Get tea}]} is not assignable to type: Store, expected an instance of Store or a snapshot like { todos: { title: string; done: boolean }[] } instead.这也是运行时类型错误runtime type error的典型输出。设计期目前设计期编译期类型检查只在TypeScript下生效MST 官方类型定义会自动从你的运行时类型推断出静态类型参见 docs/intro/welcome.md 中Static type checking with TypeScript inference from your runtime types - automatically!。基于此你可以在 IDE 中获得即时错误提示。类型系统的实现位置类型描述的核心实现集中在 src/core/type/type.tsIType、TypeFlags等与 src/core/type/type-checker.ts复杂类型模型、数组、映射见 src/types/complex-types/工具类型optional、maybe、union、reference 等见 src/types/utility-types/。五、Actions受保护的可变动作的可重放性因为状态树是活着的、可变的模型编写 action 非常直接——就地修改实例属性即可不需要像不可变范式那样手工产出新的状态树MST 的快照功能会自动为你派生新快照。默认保护规则默认情况下只有属于同一子树的 action 才能修改树。直接todo.done true会抛出对象受保护异常action 是**可重放replayable**的可以用于在多个客户端之间分发变更由于变更可以在细粒度层面被检测JSON patches 开箱即用。源码佐证节点的写保护逻辑在 src/core/node/object-node.tsassertWritable它同时检查节点是否存活assertAlive以及是否在 action 上下文中执行而 action 的包装、middleware 调度与 action 上下文IMiddlewareEvent实现在 src/core/action.ts。两种 action 写法// 长形式可持有私有函数与 volatile 局部状态 const Todo types .model({ title: types.string }) .actions(self { function setTitle(newTitle) { self.title newTitle } return { setTitle } }) // 短形式直接返回对象字面量注意 ({ const Todo types .model({ title: types.string }) .actions(self ({ setTitle(newTitle) { self.title newTitle } }))action 初始化函数会为每个实例执行一次因此self始终绑定当前实例其闭包可以用来存放 volatile 状态或仅内部可见的私有函数。可重放 action 的约束不使用 action 修改节点会抛异常建议让 action 参数可序列化某些参数如指向其他节点的相对路径可以被自动序列化action 只能修改其所属子树上的模型不要在 action 内使用this统一使用self——这保证 action 可以安全地脱绑传递pass around而无需 bind 或箭头函数包装。如果默认保护不符合需求例如你不关心可重放性或在做 PoC可以调用unprotect(tree)关闭整棵树的保护模式允许任何人直接修改树对应的protect、isProtected实现在 src/core/mst-operations.ts。六、Patches细粒度的变更流与撤销/重做修改模型不仅产生新快照还会产生一串描述改了什么的JSON-patches其结构遵循 RFC 6902export interface IJsonPatch { op: replace | add | remove path: string value?: any }Patch 的关键语义立即发射patch 在变更发生的当下就发出不等待事务结束这点与快照不同深层观察patch 监听器可用于对整个模型树做深度的、细粒度的变更观测相对路径patch 的path是相对于监听器挂载位置的路径一改多补丁单个变更如 splice 数组可能产生多个 patch可逆patch 可以被反向应用reverse apply从而支撑撤销/重做等强大模式。常用 APIonPatch(model, listener)注册 patch 监听器当模型或其任意后代被修改时触发applyPatch(model, patch)向模型应用单个 patch 或 patch 数组recordPatches(subject, filter?)记录所有 patch 与反向 patch返回IPatchRecorderpatches、inversePatches、reversedInversePatches、replay、undo等其实现位于 [src/core/mst-operations.ts](https://link.gitcode.com/i/9d38e186b6c4f652d818a96f1506683b#L177-L258。订阅树的 patch 流是**与后端服务器或其他客户端同步差异diff**的另一种手段快照适合整体同步patch 适合增量同步。七、活性保证Liveliness拒绝读取过期数据MST 一个独特的功能是活性保证当读取或写入一个已不再属于状态树的对象时MST 会抛出异常从而保护你免受闭包或缓存仍引用旧对象导致的脏读stale read。const oldTodo store.todos[0] store.removeTodo(0) function logTodo(todo) { setTimeout(() console.log(todo.title), 1000) } logTodo(store.todos[0]) store.removeTodo(0) // throws exception in one second for using an stale object!活性检查的实现节点的存活状态由NodeLifeCycle状态机与isAlive/observableIsAlive表达见 src/core/node/BaseNode.ts每次读取/写入前会调用assertAlive其错误消息会包含对象类型、死亡时路径path upon death、子路径与当前 action 名便于定位问题见 src/core/node/object-node.ts可配置策略setLivelinessChecking(mode)支持warn默认仅打印警告、error抛出异常便于调试定位、ignore静默忽略定义在 src/core/node/livelinessChecking.ts配套 APIdestroy把节点从树中移除并标记为生命终结detach则让节点脱离原树、以新树形式继续存活isAlive检查节点是否仍在树中见 src/core/mst-operations.ts。八、React, but for data模型的组合、协调与环境注入MST 的另一种观察角度源自 Daniel Earwicker 的观点是React, but for data。类比关系如下React 概念MST 对应物可组合组件可组合的models各自封装一小片状态从 props 实例化从快照props实例化 model之后用 action 管理与保护内部状态协调reconciliation应用快照时尽可能协调树节点复用相同类型的旧节点context 传递environments环境机制把依赖信息传递给深层后代内置能力一览MST 还内置了以下能力见 philosophy 文档末尾列举references引用与 identifiers标识符支持数据规范化normalization跨应用代码归一化数据引用通过标识符缓存解析目标节点相关实现见 src/types/utility-types/reference.ts 与 src/core/node/identifier-cache.ts依赖注入dependency injection通过 environment 向整棵树注入服务/上下文概念详见 docs/concepts/dependency-injection.mdAPI 为getEnv、hasEnvsrc/core/mst-operations.ts变更记录change recordingonPatch、recordPatches等循环类型定义circular type definitions支持跨文件互相引用的类型可用types.late见 src/types/utility-types/late.ts活性分析前文已述尽早发现意外缓存导致的过期读取。九、快照、补丁、动作三种分发机制的选择MST 区分了三种状态变更分发机制各自适用不同场景本仓库的 docs/concepts/ 系列文档对此有系统阐述快照snapshot树在某一时刻的完整状态。适合持久化、整体传输、时间旅行恢复请求廉价后台维护 结构共享但一次变更后的快照可能包含大量未变化的数据。补丁patch描述增量变更replace/add/remove。适合与后端/其他客户端做增量同步、实现撤销/重做立即发射、不等待事务路径相对于监听位置。动作action描述发生了什么行为名称 可序列化参数可重放。配合 middleware 可以拦截、分发到其他端再执行。实践中常见的组合本地用 action 修改状态通过 middleware/onAction把动作序列化后广播到其他客户端对方收到后重放动作即可复现一致状态而服务端持久化则多用快照或补丁。三种机制的底层支持分别对应 src/core/mst-operations.ts 中的onSnapshot/applySnapshot/getSnapshot、onPatch/applyPatch/recordPatches以及 src/core/action.ts 中的addMiddleware/onActiononAction本身是基于 middleware 的内置监听器详见 docs/concepts/actions.md。十、与 MobX / React 生态的融合由于 MST 在底层使用 MobX它可以无缝接入mobx、mobx-react-lite / mobx-react等响应式 UI 绑定将 MST 模型直接渲染进 React 组件官方文档也强调你不需要先学会 MobX 才能使用 MST类比不必懂发动机也能开车。更进一步因为 MST 开箱即用地支持快照、middleware 与可重放 action你完全可以用一棵 MobX 状态树取代 Redux 的 store reducer——动作经由 middleware 记录下来快照即 state 的序列化形式甚至可以把Redux DevTools接到 MST 上做调试。小结MST 的设计哲学可以浓缩为三句话树是有类型的活对象——形状类型与状态数据合一运行期与TypeScript设计期双重校验可变但受保护——只有 action 能改树换来可重放、可追溯、可分发快照 补丁 动作三机制齐备宁可报错不读脏数据——活性保证让任何对已脱离树对象的访问立刻暴露从根上杜绝 stale closure 问题。想要继续深入推荐按以下顺序阅读仓库内文档docs/intro/getting-started.md → docs/concepts/trees.md → docs/concepts/actions.md → docs/concepts/snapshots.md → docs/concepts/patches.md → docs/concepts/references.md源码入口可看 src/index.ts 与 src/core/mst-operations.ts测试用例在tests/core/ 下。赞分享状态管理前端【免费下载链接】mobx-state-treeFull-featured reactive state management without the boilerplate项目地址https://gitcode.com/gh_mirrors/mo/mobx-state-tree点击查看免费下载相关推荐哔哩下载姬跨平台版你的全平台B站视频下载助手哔哩下载姬跨平台版你的全平台B站视频下载助手 还在为无法离线观看喜欢的B站视频而烦恼吗哔哩下载姬跨平台版为你提供了完美的B站视频下载解决方案。这款功能强大的Eve状态管理不可变性Immer与不可变数据Eve状态管理不可变性Immer与不可变数据 你是否还在为JavaScript状态管理中的数据突变问题头疼尝试过手动复制对象却依然出现意外副作用本文将带你编程语言immudb事务日志机制如何确保数据变更可追溯immudb事务日志机制如何确保数据变更可追溯 在当今数据驱动的业务环境中数据的完整性和可追溯性变得越来越重要。传统数据库的事务日志往往是可变的这意味着一数据库安全后端上一篇Centrifugo与Graphite集成实时监控指标的持久化与分析方案下一篇终极Rainbow配色方案指南为你的编辑器注入多彩活力创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表