MobX 提供了多种工具来处理状态,包括 toJS、toJSON 和 observable.shallow。理解它们的区别和使用场景对于正确使用 MobX 至关重要。
1. toJS
基本用法
toJS 将 observable 对象深度转换为普通 JavaScript 对象。
javascriptimport { observable, toJS } from 'mobx'; const store = observable({ user: { name: 'John', age: 30, address: { city: 'New York', country: 'USA' } }, items: [1, 2, 3] }); // 转换为普通对象 const plainObject = toJS(store); console.log(plainObject); // { // user: { // name: 'John', // age: 30, // address: { city: 'New York', country: 'USA' } // }, // items: [1, 2, 3] // } // plainObject 不再是 observable console.log(isObservable(plainObject)); // false console.log(isObservable(plainObject.user)); // false console.log(isObservable(plainObject.items)); // false
使用场景
- 将 observable 对象发送到 API
- 将 observable 对象存储到 localStorage
- 将 observable 对象传递给不兼容 observable 的库
- 调试时查看状态
示例:发送到 API
javascript@action async saveData() { const plainData = toJS(this.data); await api.saveData(plainData); }
示例:存储到 localStorage
javascript@action saveToLocalStorage() { const plainState = toJS(this.state); localStorage.setItem('appState', JSON.stringify(plainState)); }
2. toJSON
基本用法
toJSON 将 observable 对象转换为 JSON 可序列化的对象。
javascriptimport { observable, toJSON } from 'mobx'; const store = observable({ user: { name: 'John', age: 30, address: { city: 'New York', country: 'USA' } }, items: [1, 2, 3] }); // 转换为 JSON 对象 const jsonObject = toJSON(store); console.log(jsonObject); // { // user: { // name: 'John', // age: 30, // address: { city: 'New York', country: 'USA' } // }, // items: [1, 2, 3] // } // 可以直接序列化为 JSON const jsonString = JSON.stringify(store); console.log(jsonString); // {"user":{"name":"John","age":30,"address":{"city":"New York","country":"USA"}},"items":[1,2,3]}
自定义 toJSON
javascriptclass User { @observable name = 'John'; @observable password = 'secret'; @observable email = 'john@example.com'; toJSON() { return { name: this.name, email: this.email // 不包含 password }; } } const user = new User(); const json = JSON.stringify(user); console.log(json); // {"name":"John","email":"john@example.com"}
使用场景
- 序列化 observable 对象为 JSON
- 发送数据到服务器
- 存储数据到数据库
- 创建 API 响应
3. observable.shallow
基本用法
observable.shallow 创建浅层可观察对象,只有顶层属性是可观察的。
javascriptimport { observable } from 'mobx'; // 深度可观察(默认) const deepStore = observable({ user: { name: 'John', age: 30 }, items: [1, 2, 3] }); // 浅层可观察 const shallowStore = observable.shallow({ user: { name: 'John', age: 30 }, items: [1, 2, 3] }); // deepStore 的嵌套对象也是可观察的 deepStore.user.name = 'Jane'; // 会触发更新 deepStore.items.push(4); // 会触发更新 // shallowStore 的嵌套对象不是可观察的 shallowStore.user.name = 'Jane'; // 不会触发更新 shallowStore.items.push(4); // 不会触发更新 // 但顶层属性的变化会触发更新 shallowStore.user = { name: 'Jane', age: 30 }; // 会触发更新 shallowStore.items = [1, 2, 3, 4]; // 会触发更新
使用场景
- 性能优化:减少需要追踪的依赖
- 避免深度追踪带来的性能问题
- 只需要追踪顶层变化
- 处理大型数据结构
示例:大型数组
javascriptclass Store { @observable.shallow items = []; constructor() { makeAutoObservable(this); } @action loadItems = async () => { const data = await fetch('/api/items').then(r => r.json()); this.items = data; // 只追踪整个数组的替换 }; }
4. observable.deep
基本用法
observable.deep 创建深度可观察对象,所有嵌套的属性都是可观察的(这是默认行为)。
javascriptimport { observable } from 'mobx'; const deepStore = observable.deep({ user: { name: 'John', age: 30, address: { city: 'New York', country: 'USA' } }, items: [1, 2, 3] }); // 所有嵌套属性都是可观察的 deepStore.user.name = 'Jane'; // 会触发更新 deepStore.user.address.city = 'Boston'; // 会触发更新 deepStore.items.push(4); // 会触发更新
5. 对比总结
| 特性 | toJS | toJSON | observable.shallow |
|---|---|---|---|
| 用途 | 转换为普通 JS 对象 | 转换为 JSON 对象 | 创建浅层可观察对象 |
| 深度 | 深度转换 | 深度转换 | 仅顶层可观察 |
| 返回值 | 普通 JS 对象 | JSON 可序列化对象 | 可观察对象 |
| 可观察性 | 不可观察 | 不可观察 | 可观察 |
| 使用场景 | API 调用、存储 | 序列化、API 响应 | 性能优化 |
6. 性能考虑
使用 shallow 优化性能
javascript// 不好的做法:深度可观察大型数组 class BadStore { @observable items = []; // 可能有数千个元素 } // 好的做法:浅层可观察 class GoodStore { @observable.shallow items = []; @action loadItems = async () => { const data = await fetch('/api/items').then(r => r.json()); this.items = data; // 只追踪数组替换 }; }
避免频繁调用 toJS
javascript// 不好的做法:频繁调用 toJS @observer class BadComponent extends React.Component { render() { const plainData = toJS(store.data); // 每次渲染都调用 return <div>{plainData.length}</div>; } } // 好的做法:缓存结果或直接使用 observable @observer class GoodComponent extends React.Component { render() { return <div>{store.data.length}</div>; // 直接使用 observable } }
7. 常见陷阱
陷阱 1:在 computed 中调用 toJS
javascript// 不好的做法 @computed get badComputed() { const plainData = toJS(this.data); return plainData.filter(item => item.active); } // 好的做法 @computed get goodComputed() { return this.data.filter(item => item.active); }
陷阱 2:忘记 shallow 的限制
javascriptconst shallowStore = observable.shallow({ items: [] }); // 不会触发更新 shallowStore.items.push(1); // 会触发更新 shallowStore.items = [1];
陷阱 3:混淆 toJS 和 toJSON
javascriptconst store = observable({ user: { name: 'John' } }); // toJS 返回普通对象 const plain = toJS(store); console.log(plain instanceof Object); // true // toJSON 返回 JSON 可序列化对象 const json = toJSON(store); console.log(JSON.stringify(json)); // {"user":{"name":"John"}}
8. 最佳实践
1. 根据需求选择可观察深度
javascript// 小型数据结构:使用深度可观察 const smallStore = observable({ config: { theme: 'dark', language: 'en' } }); // 大型数据结构:使用浅层可观察 const largeStore = observable.shallow({ items: [] // 可能有数千个元素 });
2. 在需要时才使用 toJS
javascript// 只在发送到 API 时使用 @action async sendData() { const plainData = toJS(this.data); await api.sendData(plainData); } // 在组件中直接使用 observable @observer const Component = () => { return <div>{store.data.length}</div>; };
3. 自定义 toJSON 控制序列化
javascriptclass User { @observable id = 1; @observable name = 'John'; @observable password = 'secret'; toJSON() { return { id: this.id, name: this.name // 不包含敏感信息 }; } }
总结
理解 toJS、toJSON 和 observable.shallow 的区别和使用场景:
- toJS:将 observable 转换为普通 JS 对象,用于 API 调用和存储
- toJSON:将 observable 转换为 JSON 对象,用于序列化
- observable.shallow:创建浅层可观察对象,用于性能优化
正确使用这些工具,可以构建更高效、更可维护的 MobX 应用。