MobX 性能优化的最佳实践有哪些?
MobX 本身已经做了大量性能优化——细粒度依赖追踪、自动批处理、computed 缓存,大多数场景下开箱即用就够了。真正需要手动优化的,集中在三件事上:computed 被滥用或用错了、observable 追踪了不该追踪的东西、组件粒度太粗导致重渲染范围过大。
核心思路:减少追踪范围(只让真正会变的状态变 observable)、减少计算次数(用 computed 缓存派生值)、减少渲染范围(拆小组件、延迟间接引用)。
追问
computed 和普通 getter 有什么区别?什么时候该用 computed?
computed 会缓存结果,只在依赖的 observable 变化时重新计算;普通 getter 每次访问都执行。当你需要从 observable 数据派生新值时用 computed——过滤列表、拼接字符串、计算汇总。一个值如果会被多处读取,computed 的缓存收益更大。
注意:computed 里不能有副作用(发请求、改状态),它必须是纯函数,否则缓存一致性无法保证。
observable 的深度怎么选?什么时候用 shallow?
默认 observable 会递归把对象所有层级都变成响应式,适合嵌套深、内部属性需要单独追踪的场景。observable.shallowObject 只让第一层变成响应式,内部对象保持原样。
实际项目中,列表数据用 shallow 就够了——你通常关心的是"列表变了"而不是"某个用户的名字变了"。只有确实需要追踪深层属性变化时才用深度 observable。对于不会变的配置项(API 地址、超时时间),压根不要加 observable,纯常量没必要追踪。
action 里还需要包 runInAction 吗?
不需要。action 本身就会批量处理里面的状态变更,在 action 内再套 runInAction 是多余的。runInAction 的真正用途是异步回调中修改状态——await 之后的赋值已经不在 action 作用域内,必须用 runInAction 包起来。
javascript@action async fetchData() { this.loading = true; const data = await api.getData(); // 这里已经不在 action 作用域了 runInAction(() => { this.data = data; this.loading = false; }); }
observer 组件拆多细合适?
看组件里读了几种不同的 observable。一个组件同时读 user.name、settings.theme、data.list,任何一个变化都会触发整个组件重渲染。拆成三个小组件,各自只读自己关心的数据,交叉触发就消失了。
判断标准:observable 依赖越集中越好。一块 UI 只依赖 store 的一小块数据,就值得单独抽成 observer 组件。如果整个页面只读一个 observable,拆不拆无所谓。
另外,不读 observable 的组件(纯展示的 Header、Footer)不要加 observer,加了反而增加追踪开销。
autorun、reaction、when 怎么选?
autorun:立即执行一次,之后依赖变化就重新执行。适合日志、同步等"每次变了都要做某事"的场景。reaction:只追踪数据表达式,数据变了才执行副作用回调,默认不立即执行。比 autorun 更可控,优先用 reaction。when:条件满足时执行一次就自动销毁。适合"等数据到了再做某事"的一次性逻辑,比在 autorun 里写 if 判断更清晰。
三者的返回值都是 dispose 函数,组件卸载时一定要调用,否则内存泄漏。
数组操作有什么性能坑?
避免整体重新赋值(this.items = [...this.items, item]),MobX 会对整个数组重新建立追踪。用 push、splice 等变异方法直接操作,MobX 只追踪变化的部分。
批量替换用 replace(newArray),比重新赋值高效,MobX 内部会做差异更新而不是重建整个 observable 结构。
怎么排查 MobX 的性能问题?
用 trace() 定位是哪个 computed 或 reaction 导致了多余计算。在组件 render 里调用 trace(true),控制台会输出完整的依赖链和触发原因。
用 MobX DevTools 观察每次状态变更触发了哪些 reaction,找到重渲染次数异常的组件。
如果某个 computed 计算太频繁,检查它的依赖范围是不是比预期的大——可能是间接引用了一个大对象,MobX 会追踪这个对象上所有被读取的属性。用 computed 预处理数据,把 map/filter 的结果缓存起来,避免在 observer 组件的 render 里直接遍历大列表。
写段代码
javascript// makeAutoObservable 一键搞定 observable/computed/action 标记 class Store { items = []; filter = 'all'; constructor() { makeAutoObservable(this); } get filteredItems() { return this.filter === 'all' ? this.items : this.items.filter(i => i.active); } setFilter(f) { this.filter = f; } }