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

MobX 中的 autorun、reaction 和 when 有什么区别?

2月21日 15:50

MobX 提供了三种主要的 reaction 类型:autorun、reaction 和 when。它们各有不同的使用场景和特点,理解它们的区别对于正确使用 MobX 至关重要。

1. autorun

基本用法

javascript
import { 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
  • 发送分析数据

示例:日志记录

javascript
autorun(() => { console.log('State changed:', toJS(store)); });

示例:持久化到 localStorage

javascript
autorun(() => { localStorage.setItem('appState', JSON.stringify(toJS(store))); });

2. reaction

基本用法

javascript
import { 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
  • 比较变化:可以比较新旧值
  • 可配置:提供多种配置选项

配置选项

javascript
reaction( () => store.count, (count, prevCount, reaction) => { console.log(`Count changed from ${prevCount} to ${count}`); }, { fireImmediately: true, // 立即执行 delay: 100, // 延迟执行 equals: (a, b) => a === b, // 自定义比较函数 name: 'myReaction' // 调试名称 } );

适用场景

  • 需要精确控制追踪范围
  • 需要比较新旧值
  • 需要延迟执行
  • 复杂的副作用逻辑

示例:搜索防抖

javascript
reaction( () => store.searchQuery, (query) => { // 延迟 300ms 执行搜索 debounce(() => { performSearch(query); }, 300); }, { delay: 300 } );

示例:比较新旧值

javascript
reaction( () => 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

基本用法

javascript
import { 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:返回一个清理函数

适用场景

  • 等待某个条件满足后执行操作
  • 初始化逻辑
  • 一次性副作用

示例:等待数据加载

javascript
when( () => store.isLoaded, () => { initializeApp(); } );

示例:可取消的 when

javascript
const dispose = when( () => store.userLoggedIn, () => { showWelcomeMessage(); } ); // 如果需要取消 dispose();

示例:超时处理

javascript
const dispose = when( () => store.dataLoaded, () => { console.log('Data loaded successfully'); } ); // 5秒后取消 setTimeout(() => { dispose(); console.log('Loading timeout'); }, 5000);

三者的对比

特性autorunreactionwhen
执行时机立即执行延迟执行(默认)条件满足时执行
执行次数多次多次一次
追踪范围自动追踪所有依赖精确指定追踪范围只追踪条件
返回值disposerdisposer
适用场景日志、持久化复杂副作用、比较新旧值初始化、一次性操作

选择指南

使用 autorun 当:

  • 需要立即执行
  • 需要追踪所有依赖
  • 用于简单的副作用
  • 不需要比较新旧值
javascript
autorun(() => { document.title = store.pageTitle; });

使用 reaction 当:

  • 需要精确控制追踪范围
  • 需要比较新旧值
  • 需要延迟执行
  • 需要复杂的副作用逻辑
javascript
reaction( () => store.userId, (userId, prevUserId) => { if (userId !== prevUserId) { loadUserData(userId); } } );

使用 when 当:

  • 需要等待某个条件
  • 只需要执行一次
  • 用于初始化逻辑
  • 需要可取消的操作
javascript
when( () => 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 优化比较

javascript
import { 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 应用。

标签:Mobx