Reaction反应 #
reaction 是 MobX 提供的一个更精确的响应式函数。与 autorun 不同,reaction 允许你明确指定要追踪的状态,以及状态变化时要执行的副作用。
基本用法 #
简单示例 #
javascript
import { makeAutoObservable, reaction } from 'mobx';
class Store {
count = 0;
name = 'MobX';
constructor() {
makeAutoObservable(this);
}
}
const store = new Store();
// reaction(追踪函数, 副作用函数)
const dispose = reaction(
// 追踪函数:返回要追踪的值
() => store.count,
// 副作用函数:值变化时执行
(count, previousCount) => {
console.log(`Count changed from ${previousCount} to ${count}`);
}
);
store.count = 1; // 输出: Count changed from 0 to 1
store.count = 2; // 输出: Count changed from 1 to 2
// name 变化不会触发,因为没有被追踪
store.name = 'React'; // 没有输出
工作原理 #
text
┌─────────────────────────────────────────────────────────────┐
│ reaction 工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ reaction( │
│ () => trackedValue, // 1. 追踪函数 │
│ (value, prev) => { } // 2. 副作用函数 │
│ ) │
│ │
│ 执行流程: │
│ 1. 执行追踪函数,收集依赖 │
│ 2. 追踪函数返回值变化时,执行副作用函数 │
│ 3. 副作用函数接收新值和旧值 │
│ │
└─────────────────────────────────────────────────────────────┘
与autorun的区别 #
javascript
import { autorun, reaction } from 'mobx';
// autorun:追踪所有访问的状态
autorun(() => {
console.log(`Count: ${store.count}, Name: ${store.name}`);
});
// count 或 name 变化都会触发
// reaction:只追踪返回的状态
reaction(
() => store.count,
(count) => {
console.log(`Count: ${count}`);
}
);
// 只有 count 变化才触发
选项配置 #
fireImmediately #
立即执行一次副作用函数:
javascript
reaction(
() => store.count,
(count) => {
console.log(`Count: ${count}`);
},
{ fireImmediately: true }
);
// 立即输出: Count: 0
delay #
设置延迟执行(防抖):
javascript
reaction(
() => store.searchQuery,
(query) => {
fetchResults(query);
},
{ delay: 300 } // 延迟 300ms
);
equals #
自定义比较函数:
javascript
import { comparer } from 'mobx';
// 结构比较
reaction(
() => store.position,
(pos) => {
console.log('Position changed:', pos);
},
{ equals: comparer.structural }
);
// 自定义比较
reaction(
() => store.items.length,
(length) => {
console.log('Items count:', length);
},
{ equals: (a, b) => a === b }
);
name #
设置名称便于调试:
javascript
reaction(
() => store.count,
(count) => console.log(count),
{ name: 'countWatcher' }
);
使用场景 #
精确追踪特定状态 #
javascript
import { reaction } from 'mobx';
// 只追踪用户登录状态
reaction(
() => store.isLoggedIn,
(isLoggedIn) => {
if (isLoggedIn) {
console.log('User logged in');
loadUserData();
} else {
console.log('User logged out');
clearUserData();
}
}
);
追踪对象属性 #
javascript
// 追踪对象的特定属性
reaction(
() => ({ name: store.user.name, email: store.user.email }),
(userInfo) => {
saveUserInfo(userInfo);
}
);
追踪数组长度 #
javascript
// 只追踪数组长度变化
reaction(
() => store.items.length,
(length, previousLength) => {
console.log(`Items: ${previousLength} -> ${length}`);
}
);
追踪计算值 #
javascript
// 追踪计算值变化
reaction(
() => store.filteredItems,
(items) => {
updateUI(items);
}
);
完整示例 #
搜索功能 #
javascript
import { makeAutoObservable, reaction, runInAction } from 'mobx';
class SearchStore {
query = '';
results = [];
loading = false;
constructor() {
makeAutoObservable(this);
// 使用 reaction 实现搜索
reaction(
() => this.query,
async (query) => {
if (!query.trim()) {
this.results = [];
return;
}
this.loading = true;
try {
const response = await fetch(`/api/search?q=${query}`);
const results = await response.json();
runInAction(() => {
this.results = results;
this.loading = false;
});
} catch (error) {
runInAction(() => {
this.loading = false;
});
}
},
{ delay: 300 } // 防抖
);
}
setQuery(query) {
this.query = query;
}
}
export default new SearchStore();
购物车变化通知 #
javascript
import { makeAutoObservable, reaction } from 'mobx';
class CartStore {
items = [];
constructor() {
makeAutoObservable(this);
// 监听购物车变化
reaction(
() => this.items.length,
(count, previousCount) => {
if (count > previousCount) {
this.showNotification('Item added to cart');
} else if (count < previousCount) {
this.showNotification('Item removed from cart');
}
}
);
// 监听总价变化
reaction(
() => this.total,
(total) => {
this.updateFreeShippingStatus(total);
}
);
}
get total() {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
showNotification(message) {
console.log(message);
}
updateFreeShippingStatus(total) {
if (total >= 100) {
console.log('You qualify for free shipping!');
}
}
addItem(item) {
this.items.push(item);
}
removeItem(id) {
this.items = this.items.filter(i => i.id !== id);
}
}
export default new CartStore();
路由变化处理 #
javascript
import { makeAutoObservable, reaction } from 'mobx';
class RouterStore {
currentPath = window.location.pathname;
params = {};
constructor() {
makeAutoObservable(this);
// 监听路由变化
reaction(
() => this.currentPath,
(path, previousPath) => {
console.log(`Navigation: ${previousPath} -> ${path}`);
this.handleRouteChange(path);
}
);
// 监听 popstate 事件
window.addEventListener('popstate', () => {
this.currentPath = window.location.pathname;
});
}
handleRouteChange(path) {
// 根据路径加载不同内容
switch (path) {
case '/':
this.loadHomePage();
break;
case '/about':
this.loadAboutPage();
break;
default:
this.loadNotFoundPage();
}
}
navigate(path) {
window.history.pushState({}, '', path);
this.currentPath = path;
}
loadHomePage() { }
loadAboutPage() { }
loadNotFoundPage() { }
}
export default new RouterStore();
主题切换 #
javascript
import { makeAutoObservable, reaction } from 'mobx';
class ThemeStore {
theme = localStorage.getItem('theme') || 'light';
constructor() {
makeAutoObservable(this);
// 监听主题变化,更新 DOM 和保存设置
reaction(
() => this.theme,
(theme) => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
},
{ fireImmediately: true }
);
}
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
}
setTheme(theme) {
this.theme = theme;
}
}
export default new ThemeStore();
高级用法 #
追踪多个值 #
javascript
reaction(
() => ({
count: store.count,
name: store.name,
active: store.active
}),
({ count, name, active }) => {
console.log('State changed:', { count, name, active });
}
);
条件追踪 #
javascript
reaction(
() => store.isLoggedIn ? store.user.id : null,
(userId) => {
if (userId) {
loadUserData(userId);
}
}
);
错误处理 #
javascript
reaction(
() => store.data,
async (data) => {
try {
await saveData(data);
} catch (error) {
console.error('Save failed:', error);
}
}
);
reaction vs autorun #
| 特性 | reaction | autorun |
|---|---|---|
| 追踪方式 | 显式指定 | 自动追踪 |
| 立即执行 | 否(默认) | 是 |
| 旧值访问 | 支持 | 不支持 |
| 精确控制 | 高 | 低 |
选择建议:
- 需要精确控制追踪:使用
reaction - 需要立即执行:使用
autorun或reaction({ fireImmediately: true }) - 需要访问旧值:使用
reaction - 简单副作用:使用
autorun
总结 #
reaction 的核心要点:
- 精确追踪:只追踪返回的状态
- 延迟执行:默认不立即执行
- 访问旧值:可以获取变化前后的值
- 灵活配置:支持多种选项
使用场景:
- 精确追踪特定状态变化
- 需要访问旧值的场景
- 条件触发的副作用
- 防抖处理
继续学习 When条件执行,了解条件触发的响应式函数。
最后更新:2026-03-28