乐闻世界logo
搜索文章和话题

MobX 中的 toJS、toJSON 和 observable.shallow 有什么区别?

2月21日 15:50

MobX 提供了多种工具来处理状态,包括 toJStoJSONobservable.shallow。理解它们的区别和使用场景对于正确使用 MobX 至关重要。

1. toJS

基本用法

toJS 将 observable 对象深度转换为普通 JavaScript 对象。

javascript
import { 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 可序列化的对象。

javascript
import { 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

javascript
class 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 创建浅层可观察对象,只有顶层属性是可观察的。

javascript
import { 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]; // 会触发更新

使用场景

  • 性能优化:减少需要追踪的依赖
  • 避免深度追踪带来的性能问题
  • 只需要追踪顶层变化
  • 处理大型数据结构

示例:大型数组

javascript
class 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 创建深度可观察对象,所有嵌套的属性都是可观察的(这是默认行为)。

javascript
import { 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. 对比总结

特性toJStoJSONobservable.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 的限制

javascript
const shallowStore = observable.shallow({ items: [] }); // 不会触发更新 shallowStore.items.push(1); // 会触发更新 shallowStore.items = [1];

陷阱 3:混淆 toJS 和 toJSON

javascript
const 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 控制序列化

javascript
class User { @observable id = 1; @observable name = 'John'; @observable password = 'secret'; toJSON() { return { id: this.id, name: this.name // 不包含敏感信息 }; } }

总结

理解 toJStoJSONobservable.shallow 的区别和使用场景:

  1. toJS:将 observable 转换为普通 JS 对象,用于 API 调用和存储
  2. toJSON:将 observable 转换为 JSON 对象,用于序列化
  3. observable.shallow:创建浅层可观察对象,用于性能优化

正确使用这些工具,可以构建更高效、更可维护的 MobX 应用。

标签:Mobx