5月31日 15:55

MobX 的 toJS、toJSON 和 observable.shallow 到底该怎么选?

MobX 里经常让人混淆的不是 observable 本身,而是数据离开 MobX 时该怎么处理。toJStoJSONobservable.shallow 都和“普通对象”有关,但位置不同:toJS 负责把响应式数据深度转成普通对象,toJSON 负责控制序列化输出,observable.shallow 则是在创建 observable 时只追踪顶层。## 先记住一句话

把 MobX 数据交给 API、缓存或第三方库时,用 toJS;控制 JSON.stringify 输出时,写自定义 toJSON;数据量很大且整批替换时,才考虑 observable.shallow。三者不是替代关系。

javascript
import { makeAutoObservable, observable, toJS } from 'mobx'; class UserStore { profile = { name: 'Alice', meta: { role: 'admin' } }; rows = observable.shallow.array(); constructor() { makeAutoObservable(this, { rows: observable.shallow }); } save() { return api.updateUser(toJS(this.profile)); } toJSON() { return { profile: toJS(this.profile) }; } }

三者的边界在哪里

toJS 会递归剥离 observable,新对象不再被 MobX 追踪。它适合提交表单、生成快照、写 localStorage。不要在 render 或 computed 里反复调用。

toJSON 不是 MobX 的深转换工具,更多时候是 JavaScript 的序列化钩子。JSON.stringify(obj) 会自动调用对象自己的 toJSON。它适合做字段白名单、脱敏和输出格式控制。

observable.shallow 不负责导出数据,它只改变 observable 的创建策略。默认 deep 会追踪嵌套对象,shallow 只看顶层引用。大型表格、分页结果适合它;可编辑表单通常不适合。

追问

为什么不建议在 React render 里直接 toJS?

因为 toJS 是深拷贝,不是只读一下引用。组件每次响应式更新都会生成新对象,子组件的浅比较、memo 和虚拟列表优化都会被破坏。它的取舍是换来干净对象,但放弃引用稳定性和部分性能。边界处用一次很合理,渲染路径里反复用就是踩坑。

JSON.stringify observable 时还需要先 toJS 吗?

多数普通场景不需要,MobX observable 通常可以被 JSON.stringify 序列化。真正需要 toJS 的情况,是你要先加工数据、交给只接受 plain object 的库,或明确断开响应式引用。另一个边界是敏感字段,这时自定义 toJSON 更合适。坑在于以为 toJSON 会自动深度剥离所有 observable,实际它只按你的返回值工作。

observable.shallow 为什么会导致 UI 不更新?

shallow 只追踪顶层引用,store.config.theme = 'dark' 没换掉 config 引用,所以 MobX 不会通知观察者。正确写法是替换整个顶层对象:store.config = { ...store.config, theme: 'dark' }。它的好处是减少深层追踪开销,代价是必须使用不可变更新风格。团队里如果有人习惯直接改嵌套字段,就很容易出现数据变了但页面没动。

javascript
store.config.theme = 'light'; store.config = { ...store.config, theme: 'light' };

大列表一定要用 observable.shallow 吗?

不一定,要看列表里的元素会不会被单独编辑。如果列表只是接口返回后展示、筛选、整页替换,shallow 很合适。如果每一行都有选中、编辑、校验状态,deep 或拆成行级 store 更稳。边界判断很简单:业务是否关心元素内部字段变化触发 UI。

toJS 得到的数据能再改回 store 吗?

可以,但要把它当快照,不要当双向绑定对象。toJS(store.user) 改出来的 plain object 不会影响原 store,想写回必须在 action 里显式赋值。这个边界在表单草稿里很有用:先复制一份编辑,保存时再提交。踩坑点是把快照传来传去后误以为还在响应式系统内,结果修改没有任何观察者收到通知。

标签:Mobx