如何安全试错 What-if 场景:mobx-keystone 的 sandbox 与 draft 完整实战指南
【免费下载链接】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-keystone是一款基于数据树的 MobX 状态管理库,除了模型、快照与补丁,它还内置了两个被低估的"安全网"功能:sandbox沙盒与draft草稿。前者让你在副本中大胆尝试"如果……会怎样"(What-if)而不影响真实状态,后者让"保存/重置"式的表单编辑变得简单可靠。
🤔 为什么你需要 What-if 场景能力
在日常开发中,这类需求非常常见:
| 场景 | 想要的效果 |
|---|---|
| 用户点击"添加商品"前 | 先验证加入后是否会产生校验错误,但不真正加入 |
| 设置表单 | 用户可以随意修改,点"保存"才生效,点"重置"全部还原 |
| 批量操作 | 先模拟一遍操作结果,确认无误再真正提交 |
如果直接在真实状态上改,一旦后悔就得手动回滚,很容易写错。而 mobx-keystone 的思路是:在一份与真实数据完全同步的副本上操作,最后决定"提交"还是"丢弃"。
这正是sandbox和draft的分工所在。
🧪 sandbox 沙盒:可提交的临时实验场
sandbox函数为某个子树创建一个沙盒管理器,随后通过withSandbox在副本上执行任何 action:
- 回调返回
true→ 沙盒内的所有改动被提交到原始状态; - 回调返回
false→ 所有改动整体回滚,就像什么都没发生; - 还可以返回
{ commit: false, return: 某个值 },既丢弃改动,又带走一份"预演结果"——这就是 What-if 的核心。
举个经典例子:模型里有个计算属性isValid,我们想验证"如果把值改成 2 是否合法",但又不想真的改:
const isValid = numSandbox.withSandbox([num], (numCopy) => { numCopy.setValue(2) return { commit: false, return: numCopy.isValid } })整个过程原始数据纹丝不动,你却拿到了真实状态下的校验结论。几个关键特性值得记住:
- 自动同步:原始树变化时,沙盒副本会自动跟进,始终反映最新状态;
- 嵌套支持:
withSandbox可以套娃调用,只有最外层决定是否提交; - 异常安全:回调中抛出错误时,改动自动被拒绝,不会留下半成品;
- 可识别:用
isSandboxedNode(node)可以判断一个节点是否处于沙盒中,方便让某些reaction只在"真实"节点上运行; - 记得释放:沙盒用完后调用
dispose()清理监听。
💡 实现细节:沙盒通过补丁(patches)机制追踪副本上的每一步变更,提交时把它们应用到原树,回滚时用逆补丁撤销——这也是为什么一次沙盒提交在 undo 中间件里只算一个撤销步骤。相关源码见 sandbox.ts。
📝 draft 草稿:为"保存 / 重置"而生的编辑副本
如果说 sandbox 是"一次性的实验",那draft就是"长期存在的编辑区"。一次调用draft(originalObject)就能得到一个草稿对象,它把最常见的表单需求全部封装好了:
| 成员 | 作用 |
|---|---|
data | 可编辑的草稿副本,直接在上面调用 action 修改 |
originalData | 原始对象引用,方便对比 |
commit() | 把草稿的全部改动提交到原对象(点"保存") |
commitByPath(path) | 只提交指定路径的改动(部分保存) |
reset() | 丢弃改动,草稿恢复为原对象的最新状态(点"重置") |
resetByPath(path) | 只还原指定路径 |
isDirty | 响应式布尔值:草稿是否有未提交的改动 |
isDirtyByPath(path) | 查询某个路径是否被修改过 |
以"偏好设置"表单为例,官方文档给出的思路非常清爽:
const preferencesDraft = draft(myRootStore.preferences)然后把preferencesDraft.data传给表单组件,保存按钮调commit(),重置按钮调reset()。由于isDirty是一个计算值,你可以用它驱动 UI:没有改动时禁用"保存"按钮,有改动时高亮"重置"按钮——不需要自己手写任何脏检查逻辑。
🔍 与 sandbox 不同,reset()会以原对象当前的最新状态为基准重置。也就是说,如果草稿打开期间原始数据被别的途径修改了,重置后草稿会同步到最新值,而不是回到创建草稿时的旧值。草稿的实现在 draft.ts,它内部基于快照(snapshot)与深度比较实现isDirty判断。
⚖️ sandbox 还是 draft?一张表看懂怎么选
| 维度 | sandbox | draft |
|---|---|---|
| 使用模式 | 同步回调内完成,结束即定去留 | 长生命周期对象,随时提交/重置 |
| 典型场景 | What-if 预演、合法性验证 | 设置表单、编辑器的"未保存更改" |
| 回滚方式 | 回调返回false自动回滚 | 手动调用reset() |
| 部分提交 | 整个回调结果统一提交 | commitByPath支持按路径提交 |
| 脏检查 | 无需(提交即结束) | isDirty/isDirtyByPath |
| 生命周期 | 用完需dispose() | 无需显式销毁 |
一句话总结:"先验后做"选 sandbox,"边改边存"选 draft。两者都位于 treeUtils 模块,从mobx-keystone主包直接导入即可。
📚 延伸阅读与源码位置
想深入细节,推荐按这个顺序看:
- 沙盒文档:
apps/site/docs/sandboxes.mdx,含嵌套调用与多态校验的完整示例 - 草稿文档:
apps/site/docs/drafts.mdx,含设置表单的组件接线示范 - 沙盒实现:
packages/lib/src/treeUtils/sandbox.ts(补丁记录与逆补丁回滚) - 草稿实现:
packages/lib/src/treeUtils/draft.ts(快照对比与路径级提交) - 沙盒行为测试:
packages/lib/test/treeUtils/sandbox.test.ts,可以读到官方对边界行为的明确约定(如"withSandbox 执行期间原树不允许变化") - 草稿行为测试:
packages/lib/test/treeUtils/draft.test.ts
🎯 小结
sandbox+withSandbox:在副本上大胆尝试,返回true提交、返回false回滚,还能顺手带回预演结果,是 What-if 场景的瑞士军刀;draft:自带commit/reset/isDirty的可编辑草稿,是设置表单这类"保存/重置"工作流的最佳拍档;- 两者都建立在 mobx-keystone 的补丁与快照体系之上,天然兼容 undo 中间件、只读保护等能力,让你在"单一数据源"的纪律下依然拥有安全试错的自由。
【免费下载链接】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),仅供参考