MST用户注意:mobx-keystone与mobx-state-tree的10大核心差异+迁移指南

📅 2026/8/27 17:26:20
MST用户注意:mobx-keystone与mobx-state-tree的10大核心差异+迁移指南
MST用户注意mobx-keystone与mobx-state-tree的10大核心差异迁移指南【免费下载链接】mobx-keystoneA MobX powered state management solution based on data trees with first class support for Typescript, support for snapshots, patches and much more项目地址: https://gitcode.com/gh_mirrors/mo/mobx-keystone如果你正在使用mobx-state-treeMST做 TypeScript 状态管理或正在评估是否迁移到mobx-keystone——这个基于数据树的 MobX 状态管理方案这篇文章值得你花 10 分钟读完。下面梳理了两者最容易踩坑的 10 大核心差异并附上一份可直接执行的迁移指南帮你低成本完成切换。 一张表看懂功能对照总览先说结论mobx-keystone借鉴了 MST 的大量思想但整体设计对 TypeScript 更友好。核心能力对照如下能力mobx-keystonemobx-state-tree树形状态结构✅✅不可变快照Snapshot✅✅JSON Patch 生成✅✅动作序列化 / 回放✅✅动作中间件含事务、撤销✅ 更完善✅Flow 异步动作✅✅引用References✅ 显式对象✅ 标识符风格TypeScript 支持⭐⭐ 更强⭐ 一般实例/快照类型使用简化✅❌ 需大量cast模型生命周期简化✅ 仅 2 个钩子❌ 懒初始化有坑运行时类型校验✅ 完全可选✅ 强制内置Redux 兼容层✅✅ 一句话总结能力基本对齐类型体验显著升级生命周期更可控。 10大核心差异详解差异 1模型定义 ——types.modelvs TypeScript 类MST 用链式函数声明模型mobx-keystone直接让你写TypeScript 类// MST 风格 const Todo types.model(Todo, { text: types.string, done: types.optional(types.boolean, false) }) // mobx-keystone 风格类模型见 apps/site/docs/classModels.mdx model(myApp/Todo) class Todo extends Model({ text: propstring(), done: prop(false), }) {}好处IDE 补全、重构、继承全部原生可用学习曲线更平。差异 2selfvsthisMST 里跨块访问要用self同块访问用this经常让人困惑。mobx-keystone一律使用this计算属性直接用标准的 MobXcomputed装饰器。差异 3运行时类型校验完全可选MST 的类型校验是强制的types.string等 schema 必写。mobx-keystone提供两种模式只信 TypeScript用propT()零运行时开销需要运行时校验如不可信数据源用tProp(types.string)。差异 4递归 / 交叉引用模型变简单MST 里自递归或相互引用的模型需要types.late、types.optional等绕弯子类型推断经常失效。mobx-keystone中直接互相引用类即可无需 late 类型、无需强制类型转换。差异 5引用从字符串标识符变成显式Ref对象MSTmobx-keystone声明types.reference(Todo)propRefTodo()rootRef/customRef快照形态存 ID 字符串存模型快照如{ id, $modelType }safeReference内建自动行为通过onResolvedValueChange显式实现策略访问值self.selectedTodo直接是实例computed中读ref?.maybeCurrent⚠️这是持久化数据迁移的最大坑旧快照里的引用形态与新系统不同需要数据转换下文迁移指南有方案。差异 6生命周期钩子从 4 个减到 2 个MST 有beforeCreate/afterCreate/afterAttach/beforeDetach等钩子且节点是懒初始化的——你写的afterCreate可能在节点内容被访问前根本不执行getRoot也可能拿不到你以为的值。mobx-keystone只保留两个语义明确的钩子onInit—— 模型创建后必然触发没有懒初始化onAttachedToRootStore—— 挂到已注册的根存储时触发可返回清理函数disposer是注册副作用reaction 等的安全位置。差异 7volatile不再是特殊概念MST 的.volatile(() ({...}))在mobx-keystone中就是普通类字段需要响应式时加上 MobX 的observableaction即可。运行期临时数据不进入快照语义不变但写法更直白。差异 8环境注入getEnv→createContextMST 通过getEnv(self)从树根读取依赖api、router 等。mobx-keystone的等价物是Context见packages/lib/src/context/const envCtx createContextEnv() // 模型内部取用 const env envCtx.get(this)!好处是依赖注入与模型解耦单测更容易。差异 9异步 Flow 的写法变化MSTload: flow(function* () { const dto yield api.fetch() })mobx-keystone方法加modelFlow装饰器yield promise改为yield* _await(promise)。功能等价只是语法糖不同。差异 10destroy/isAlive的死节点语义被移除MST 里节点被destroy后进入死亡状态误用会抛错。mobx-keystone没有死节点概念detach(node)之后实例仍是完全可用的普通对象不会再报 isAlive 错误。这对减少幽灵崩溃很友好但也要更新测试用例的断言。️ MST → mobx-keystone 迁移指南可直接执行官方完整迁移文档位于apps/site/docs/mstMigrationGuide.mdx功能对比表在apps/site/docs/mstComparison.mdx。以下是浓缩版操作手册。第一步迁移前必须先做的 3 个决策是否要运行时类型校验—— 只靠 TypeScript 就用propT()需要运行时校验更接近 MST 的 schema 习惯、利于快照迁移就用tProp(...)types.*持久化格式—— MST 快照没有$modelType元数据mobx-keystone模型快照会带上$modelType。老快照需要用带类型的fromSnapshot(Todo, oldSnapshot)加载若属性用了tProp其内部快照通常可省略$modelType引用策略—— 明确哪些关系是真正的树子节点哪些应该是Ref跨树引用。第二步推荐的 7 步迁移顺序保持 diff 可审查转换模型定义数据结构 基础 actions/views转换异步 flowflow→modelFlow转换环境注入getEnv→createContext转换引用types.reference/safeReference→RefrootRef/customRef转换持久化与快照重点是旧快照数据迁移转换 patch / 动作回放 / 中间件集成跑测试修复边缘场景生命周期、集合、快照处理器第三步高频坑位速查 ⚠️坑说明处理办法隐式快照赋值MST 常把快照直接赋给实例类型的属性配合cast改为fromSnapshot或applySnapshot显式转换数组默认值mobx-keystone数组默认拒绝undefined元素保证 JSON 兼容建模时用null/联合类型或开启setGlobalConfig({ allowUndefinedArrayElements: true })数字 IDRef要求字符串 ID保留数字字段但重写getRefId()返回String(id)ID 可变性MST 的 identifier 实际不可变keystone 的idProp可以在 action 里改靠约定或测试守卫只写一次快照处理器MST 的preProcessSnapshot/snapshotProcessor模型级Model(props, { fromSnapshotProcessor, toSnapshotProcessor })属性级.withSnapshotProcessor(...)第四步高频 API 对照表收藏向MSTmobx-keystone备注types.model(Name, {...})model(app/Name) class X extends Model({...})类型名全应用唯一types.compose(A, B)class B extends ExtendedModel(A, {...})类继承方式组合types.identifieridProp推荐 ID 字段types.array(T)propT[](() [])默认工厂必须显式写types.DatetProp(types.dateAsTimestamp)或dateAsIsoStringcodec 类型types.union(A, B)types.or(A, B)注意改名.views((self) ...)computedgetter / 普通方法全部改用this.actions((self) ...)modelAction方法—flow(function*...)modelFlow *m() { yield* _await(...) }异步动作getEnv(self)createContext(...).get(this)依赖注入.volatile(...)类字段需响应式则加observable不进快照afterCreate/afterAttachonInit/onAttachedToRootStore语义更可靠onPatch/applyPatchonPatches/applyPatches注意复数形式onAction/addMiddlewareonActionMiddleware/addActionMiddleware中间件体系更完善destroy(node)detach(node)或在 action 中移出父级无死节点错误clone(node)clone(node)同名默认生成新 ID相关实现可参考源码目录packages/lib/src/ref/引用、packages/lib/src/snapshot/快照、packages/lib/src/action/动作与中间件、packages/lib/src/context/上下文注入。✅ 迁移完成检查清单模型在严格模式 TypeScript 下编译通过所有self已替换为thiscast(...)基本移除动作 / flow 的变更边界仍被正确保护引用解析与清理行为符合预期含 safe-reference 策略快照存取往返测试通过含$modelType元数据types.map/types.array的默认值已显式提供volatile 状态已迁移为类字段且响应式正确patch / 动作回放链路验证通过onPatches/applyPatchesregisterRootStore已为根存储注册生命周期钩子需要既有测试全部通过并为迁移的边缘场景新增测试 小结mobx-keystone保留了 MST 你最熟悉的一切——数据树、快照、patch、动作回放、undo/redo——同时用类模型 可选运行时校验 简化的生命周期解决了 MST 长期被诟病的类型痛点。如果你的项目已经用上了 TypeScript 严格模式迁移的成本比想象中低按上面的 7 步顺序推进重点盯住引用快照格式和生命周期行为这两处语义变化即可。【免费下载链接】mobx-keystoneA MobX powered state management solution based on data trees with first class support for Typescript, support for snapshots, patches and much more项目地址: https://gitcode.com/gh_mirrors/mo/mobx-keystone创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考