MobX 是一个功能强大的状态管理库,但在使用过程中可能会遇到一些常见问题。了解这些问题及其解决方案可以帮助开发者更好地使用 MobX。
1. 组件不更新
问题描述
组件使用 observer 包装,但状态变化时组件不更新。
常见原因和解决方案
原因 1:访问的是普通对象而不是 observable
javascript// 错误 const store = { count: 0 }; @observer class Counter extends React.Component { render() { return <div>{store.count}</div>; // 不会更新 } } // 正确 import { observable } from 'mobx'; const store = observable({ count: 0 }); @observer class Counter extends React.Component { render() { return <div>{store.count}</div>; // 会更新 } }
原因 2:在 action 外修改状态(MobX 6)
javascript// 错误(MobX 6) class Store { @observable count = 0; increment() { this.count++; // 不在 action 中,会报错 } } // 正确 class Store { @observable count = 0; @action increment() { this.count++; // 在 action 中 } }
原因 3:在 render 中创建新对象
javascript// 错误 @observer class Component extends React.Component { render() { const style = { color: 'red' }; // 每次渲染都创建新对象 return <div style={style}>{store.count}</div>; } } // 正确 const style = { color: 'red' }; // 在组件外部定义 @observer class Component extends React.Component { render() { return <div style={style}>{store.count}</div>; } }
2. 性能问题
问题描述
应用性能下降,组件频繁重新渲染。
常见原因和解决方案
原因 1:过度追踪
javascript// 错误:在循环中访问 observable @observer class List extends React.Component { render() { return ( <div> {store.items.map(item => ( <div key={item.id}> {item.name} - {item.value} - {item.description} </div> ))} </div> ); } } // 正确:使用 computed 预处理数据 class Store { @observable items = []; @computed get itemDisplayData() { return this.items.map(item => ({ id: item.id, display: `${item.name} - ${item.value} - ${item.description}` })); } } @observer class List extends React.Component { render() { return ( <div> {store.itemDisplayData.map(item => ( <div key={item.id}>{item.display}</div> ))} </div> ); } }
原因 2:组件依赖太多状态
javascript// 错误:组件依赖太多状态 @observer class Dashboard extends React.Component { render() { return ( <div> <UserInfo /> <Settings /> <DataCount /> </div> ); } } // 正确:拆分为多个组件 @observer class UserInfo extends React.Component { render() { return ( <div> <div>{store.user.name}</div> <div>{store.user.email}</div> </div> ); } }
3. 内存泄漏
问题描述
组件卸载后,reaction 仍然在运行,导致内存泄漏。
解决方案
javascript// 错误:忘记清理 reaction useEffect(() => { autorun(() => { console.log(store.count); }); }, []); // 正确:清理 reaction useEffect(() => { const dispose = autorun(() => { console.log(store.count); }); return () => { dispose(); // 清理 reaction }; }, []);
4. 异步操作问题
问题描述
异步操作中的状态修改不生效。
解决方案
javascript// 错误:异步操作中的状态修改 @action async fetchData() { this.loading = true; const data = await fetch('/api/data').then(r => r.json()); this.data = data; // 不在 action 中 this.loading = false; // 不在 action 中 } // 正确:使用 runInAction @action async fetchData() { this.loading = true; try { const data = await fetch('/api/data').then(r => r.json()); runInAction(() => { this.data = data; }); } finally { runInAction(() => { this.loading = false; }); } } // 或者使用 flow fetchData = flow(function* () { this.loading = true; try { const response = yield fetch('/api/data'); const data = yield response.json(); this.data = data; } finally { this.loading = false; } });
5. computed 不更新
问题描述
computed 属性没有按预期更新。
常见原因和解决方案
原因 1:在 computed 中产生副作用
javascript// 错误:在 computed 中产生副作用 @computed get badComputed() { console.log('Side effect!'); // 不应该在 computed 中 fetch('/api/data'); // 不应该在 computed 中 return this.data; } // 正确:computed 应该是纯函数 @computed get goodComputed() { return this.data.filter(item => item.active); }
原因 2:依赖项没有正确追踪
javascript// 错误:依赖项没有正确追踪 @computed get badComputed() { const data = this.data; // 没有在返回值中使用 return this.items.length; } // 正确:依赖项正确追踪 @computed get goodComputed() { return this.data.length + this.items.length; }
6. 循环依赖
问题描述
多个 store 之间存在循环依赖,导致无限循环或性能问题。
解决方案
javascript// 错误:循环依赖 class StoreA { @observable value = 0; @computed get doubled() { return storeB.value * 2; } } class StoreB { @observable value = 0; @computed get doubled() { return storeA.value * 2; } } // 正确:避免循环依赖 class Store { @observable valueA = 0; @observable valueB = 0; @computed get doubledA() { return this.valueA * 2; } @computed get doubledB() { return this.valueB * 2; } }
7. 装饰器不工作
问题描述
使用装饰器时出现错误或不生效。
解决方案
确保配置正确
javascript// package.json { "babel": { "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }] ] } }
或者使用 makeObservable
javascript// 不使用装饰器 class Store { count = 0; constructor() { makeObservable(this, { count: observable }); } }
8. TypeScript 类型错误
问题描述
使用 TypeScript 时出现类型错误。
解决方案
javascript// 错误:没有类型参数 class Store { count = 0; constructor() { makeObservable(this, { count: observable }); } } // 正确:使用类型参数 class Store { count: number = 0; constructor() { makeObservable<Store>(this, { count: observable }); } }
9. 数组操作问题
问题描述
数组操作不触发更新。
解决方案
javascript// 错误:重新赋值整个数组 @action badAddItem(item) { this.items = [...this.items, item]; } // 正确:使用数组方法 @action goodAddItem(item) { this.items.push(item); } // 或者使用 replace @action replaceItems(newItems) { this.items.replace(newItems); }
10. 调试困难
问题描述
难以追踪状态变化和依赖关系。
解决方案
使用 trace
javascriptimport { trace } from 'mobx'; // 追踪 computed trace(store.fullName); // 追踪组件渲染 @observer class MyComponent extends React.Component { render() { trace(true); // 追踪组件渲染 return <div>{store.count}</div>; } }
使用 MobX DevTools
javascriptimport { configure } from 'mobx'; configure({ // 启用调试模式 useProxies: 'ifavailable', isolateGlobalState: true });
最佳实践总结
- 始终在 action 中修改状态(MobX 6)
- 使用 observer 包装需要响应状态变化的组件
- 避免在 render 中创建新对象
- 使用 computed 优化计算逻辑
- 及时清理 reaction 和副作用
- 正确处理异步操作
- 避免循环依赖
- 使用 trace 和 DevTools 调试
- 合理拆分组件以减少依赖
- 遵循 MobX 的最佳实践
遵循这些最佳实践,可以避免大多数常见的 MobX 问题,构建稳定、高效的应用。