MobX 提供了三种主要的 reaction 类型:autorun、reaction 和 when。它们各有不同的使用场景和特点,理解它们的区别对于正确使用 MobX 至关重要。
1. autorun
基本用法
javascriptimport { autorun } from 'mobx'; const store = observable({ count: 0 }); autorun(() => { console.log(`Count is: ${store.count}`); }); store.count++; // 输出: Count is: 1 store.count++; // 输出: Count is: 2
特点
- 立即执行:autorun 会在创建时立即执行一次
- 自动追踪:自动追踪函数中访问的所有 observable
- 自动重新执行:当依赖的 observable 变化时自动重新执行
- 无返回值:不能返回值,主要用于副作用
适用场景
- 日志记录
- 数据持久化
- 同步状态到 localStorage
- 发送分析数据
示例:日志记录
javascriptautorun(() => { console.log('State changed:', toJS(store)); });
示例:持久化到 localStorage
javascriptautorun(() => { localStorage.setItem('appState', JSON.stringify(toJS(store))); });
2. reaction
基本用法
javascriptimport { reaction } from 'mobx'; const store = observable({ count: 0, name: 'John' }); reaction( () => store.count, // 追踪函数 (count, reaction) => { // 效果函数 console.log(`Count changed to: ${count}`); }, { fireImmediately: false } // 配置选项 ); store.count++; // 输出: Count changed to: 1 store.name = 'Jane'; // 不会触发,因为只追踪 count
特点
- 延迟执行:默认情况下不会立即执行
- 精确控制:可以精确指定要追踪的 observable
- 比较变化:可以比较新旧值
- 可配置:提供多种配置选项
配置选项
javascriptreaction( () => store.count, (count, prevCount, reaction) => { console.log(`Count changed from ${prevCount} to ${count}`); }, { fireImmediately: true, // 立即执行 delay: 100, // 延迟执行 equals: (a, b) => a === b, // 自定义比较函数 name: 'myReaction' // 调试名称 } );
适用场景
- 需要精确控制追踪范围
- 需要比较新旧值
- 需要延迟执行
- 复杂的副作用逻辑
示例:搜索防抖
javascriptreaction( () => store.searchQuery, (query) => { // 延迟 300ms 执行搜索 debounce(() => { performSearch(query); }, 300); }, { delay: 300 } );
示例:比较新旧值
javascriptreaction( () => store.items, (items, prevItems) => { const added = items.filter(item => !prevItems.includes(item)); const removed = prevItems.filter(item => !items.includes(item)); console.log('Added:', added); console.log('Removed:', removed); }, { equals: comparer.structural } // 深度比较 );
3. when
基本用法
javascriptimport { when } from 'mobx'; const store = observable({ loaded: false, data: null }); when( () => store.loaded, // 条件函数 () => { // 效果函数 console.log('Data loaded:', store.data); } ); store.loaded = true; store.data = { name: 'John' }; // 输出: Data loaded: { name: 'John' }
特点
- 一次性执行:条件满足后只执行一次
- 自动清理:执行后自动清理
- 可取消:可以手动取消
- 返回 disposer:返回一个清理函数
适用场景
- 等待某个条件满足后执行操作
- 初始化逻辑
- 一次性副作用
示例:等待数据加载
javascriptwhen( () => store.isLoaded, () => { initializeApp(); } );
示例:可取消的 when
javascriptconst dispose = when( () => store.userLoggedIn, () => { showWelcomeMessage(); } ); // 如果需要取消 dispose();
示例:超时处理
javascriptconst dispose = when( () => store.dataLoaded, () => { console.log('Data loaded successfully'); } ); // 5秒后取消 setTimeout(() => { dispose(); console.log('Loading timeout'); }, 5000);
三者的对比
| 特性 | autorun | reaction | when |
|---|---|---|---|
| 执行时机 | 立即执行 | 延迟执行(默认) | 条件满足时执行 |
| 执行次数 | 多次 | 多次 | 一次 |
| 追踪范围 | 自动追踪所有依赖 | 精确指定追踪范围 | 只追踪条件 |
| 返回值 | 无 | disposer | disposer |
| 适用场景 | 日志、持久化 | 复杂副作用、比较新旧值 | 初始化、一次性操作 |
选择指南
使用 autorun 当:
- 需要立即执行
- 需要追踪所有依赖
- 用于简单的副作用
- 不需要比较新旧值
javascriptautorun(() => { document.title = store.pageTitle; });
使用 reaction 当:
- 需要精确控制追踪范围
- 需要比较新旧值
- 需要延迟执行
- 需要复杂的副作用逻辑
javascriptreaction( () => store.userId, (userId, prevUserId) => { if (userId !== prevUserId) { loadUserData(userId); } } );
使用 when 当:
- 需要等待某个条件
- 只需要执行一次
- 用于初始化逻辑
- 需要可取消的操作
javascriptwhen( () => store.initialized, () => { startApp(); } );
性能考虑
1. 避免过度追踪
javascript// 不好的做法:autorun 追踪太多 autorun(() => { console.log(store.user.name, store.user.email, store.user.age); }); // 好的做法:reaction 精确追踪 reaction( () => store.user.name, (name) => { console.log(name); } );
2. 及时清理
javascript// 在组件卸载时清理 useEffect(() => { const dispose = autorun(() => { console.log(store.count); }); return () => { dispose(); // 清理 reaction }; }, []);
3. 使用 comparer 优化比较
javascriptimport { comparer } from 'mobx'; reaction( () => store.items, (items) => { console.log('Items changed'); }, { equals: comparer.structural } // 深度比较,避免不必要的更新 );
常见陷阱
1. 在 reaction 中产生副作用
javascript// 不好的做法:在追踪函数中产生副作用 reaction( () => { console.log('Side effect!'); // 不应该在追踪函数中 return store.count; }, (count) => { console.log(count); } ); // 好的做法:追踪函数应该是纯函数 reaction( () => store.count, (count) => { console.log('Side effect:', count); // 副作用在效果函数中 } );
2. 忘记清理 reaction
javascript// 不好的做法:忘记清理 useEffect(() => { autorun(() => { console.log(store.count); }); // 没有清理函数 }, []); // 好的做法:清理 reaction useEffect(() => { const dispose = autorun(() => { console.log(store.count); }); return () => dispose(); }, []);
3. 滥用 autorun
javascript// 不好的做法:使用 autorun 处理一次性操作 autorun(() => { if (store.initialized) { initializeApp(); // 会多次执行 } }); // 好的做法:使用 when 处理一次性操作 when( () => store.initialized, () => { initializeApp(); // 只执行一次 } );
总结
理解 autorun、reaction 和 when 的区别和使用场景是掌握 MobX 的关键:
- autorun:用于简单的、需要立即执行的副作用
- reaction:用于需要精确控制、比较新旧值的复杂副作用
- when:用于等待条件满足后执行的一次性操作
正确选择和使用这些 reaction 类型,可以构建更高效、更可维护的 MobX 应用。