runInAction #
runInAction 是 MobX 提供的一个工具函数,用于在异步操作中安全地更新状态。它允许你在 action 外部执行状态修改,同时保持 action 的特性。
为什么需要runInAction? #
异步操作的问题 #
当配置了 enforceActions: 'always' 时,所有状态修改必须在 action 中进行。但异步操作中的状态修改会出错:
javascript
import { configure, makeAutoObservable } from 'mobx';
configure({ enforceActions: 'always' });
class Store {
data = null;
loading = false;
constructor() {
makeAutoObservable(this);
}
async fetchData() {
this.loading = true; // OK:在 action 中
const response = await fetch('/api/data');
const data = await response.json();
this.data = data; // Error:不在 action 中!
this.loading = false; // Error:不在 action 中!
}
}
使用runInAction解决 #
javascript
import { runInAction } from 'mobx';
class Store {
data = null;
loading = false;
constructor() {
makeAutoObservable(this);
}
async fetchData() {
this.loading = true;
try {
const response = await fetch('/api/data');
const data = await response.json();
// 使用 runInAction 包装状态修改
runInAction(() => {
this.data = data;
this.loading = false;
});
} catch (error) {
runInAction(() => {
this.loading = false;
});
}
}
}
基本用法 #
简单示例 #
javascript
import { runInAction } from 'mobx';
// 在异步操作中更新状态
setTimeout(() => {
runInAction(() => {
store.count++;
});
}, 1000);
// 在 Promise 中更新状态
fetch('/api/data')
.then(res => res.json())
.then(data => {
runInAction(() => {
store.data = data;
});
});
返回值 #
runInAction 可以返回值:
javascript
const result = runInAction(() => {
store.count++;
return store.count;
});
console.log(result); // 新的 count 值
使用flow替代 #
MobX 提供了 flow 作为更优雅的异步处理方案:
使用flow #
javascript
import { makeAutoObservable, flow } from 'mobx';
class Store {
data = null;
loading = false;
error = null;
constructor() {
makeAutoObservable(this);
}
// 使用 flow 处理异步
fetchData = flow(function* () {
this.loading = true;
this.error = null;
try {
const response = yield fetch('/api/data');
const data = yield response.json();
this.data = data;
this.error = null;
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
});
}
flow vs runInAction #
| 特性 | flow | runInAction |
|---|---|---|
| 语法 | Generator函数 | 普通函数 |
| 可读性 | 更好 | 一般 |
| 错误处理 | try/catch | try/catch |
| 取消支持 | 支持 | 不支持 |
| 类型推断 | 更好 | 一般 |
完整示例 #
API请求Store #
javascript
import { makeAutoObservable, runInAction } from 'mobx';
class ApiStore {
data = null;
loading = false;
error = null;
constructor() {
makeAutoObservable(this);
}
// GET 请求
async get(url) {
this.loading = true;
this.error = null;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
runInAction(() => {
this.data = data;
this.loading = false;
});
return data;
} catch (error) {
runInAction(() => {
this.error = error.message;
this.loading = false;
});
throw error;
}
}
// POST 请求
async post(url, body) {
this.loading = true;
this.error = null;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
runInAction(() => {
this.data = data;
this.loading = false;
});
return data;
} catch (error) {
runInAction(() => {
this.error = error.message;
this.loading = false;
});
throw error;
}
}
// 清除数据
clear() {
this.data = null;
this.error = null;
}
}
export default new ApiStore();
分页数据Store #
javascript
import { makeAutoObservable, runInAction } from 'mobx';
class PaginationStore {
items = [];
page = 1;
pageSize = 10;
total = 0;
loading = false;
hasMore = true;
constructor() {
makeAutoObservable(this);
}
async loadMore(fetchFn) {
if (this.loading || !this.hasMore) return;
this.loading = true;
try {
const response = await fetchFn(this.page, this.pageSize);
runInAction(() => {
this.items = [...this.items, ...response.items];
this.total = response.total;
this.page++;
this.hasMore = this.items.length < this.total;
this.loading = false;
});
} catch (error) {
runInAction(() => {
this.loading = false;
});
throw error;
}
}
async refresh(fetchFn) {
this.loading = true;
try {
const response = await fetchFn(1, this.pageSize);
runInAction(() => {
this.items = response.items;
this.total = response.total;
this.page = 2;
this.hasMore = this.items.length < this.total;
this.loading = false;
});
} catch (error) {
runInAction(() => {
this.loading = false;
});
throw error;
}
}
reset() {
this.items = [];
this.page = 1;
this.total = 0;
this.hasMore = true;
}
}
搜索Store #
javascript
import { makeAutoObservable, runInAction } from 'mobx';
class SearchStore {
query = '';
results = [];
loading = false;
error = null;
searchTimeout = null;
constructor() {
makeAutoObservable(this);
}
// 防抖搜索
search(query) {
this.query = query;
// 清除之前的定时器
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
// 空查询时清空结果
if (!query.trim()) {
this.results = [];
return;
}
// 设置新的定时器
this.searchTimeout = setTimeout(async () => {
this.loading = true;
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const results = await response.json();
runInAction(() => {
this.results = results;
this.loading = false;
});
} catch (error) {
runInAction(() => {
this.error = error.message;
this.loading = false;
});
}
}, 300);
}
clear() {
this.query = '';
this.results = [];
this.error = null;
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
}
}
使用flow重构 #
使用 flow 可以让代码更简洁:
javascript
import { makeAutoObservable, flow } from 'mobx';
class UserStore {
users = [];
loading = false;
error = null;
constructor() {
makeAutoObservable(this);
}
fetchUsers = flow(function* () {
this.loading = true;
this.error = null;
try {
const response = yield fetch('/api/users');
const users = yield response.json();
this.users = users;
this.error = null;
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
});
createUser = flow(function* (userData) {
this.loading = true;
try {
const response = yield fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
const user = yield response.json();
this.users.push(user);
return user;
} catch (error) {
this.error = error.message;
throw error;
} finally {
this.loading = false;
}
});
deleteUser = flow(function* (id) {
try {
yield fetch(`/api/users/${id}`, { method: 'DELETE' });
this.users = this.users.filter(u => u.id !== id);
} catch (error) {
this.error = error.message;
throw error;
}
});
}
最佳实践 #
1. 统一错误处理 #
javascript
class Store {
async safeRequest(requestFn, onSuccess, onError) {
try {
const result = await requestFn();
runInAction(() => onSuccess(result));
return result;
} catch (error) {
runInAction(() => onError(error));
throw error;
}
}
async fetchData() {
return this.safeRequest(
() => fetch('/api/data').then(r => r.json()),
(data) => { this.data = data; },
(error) => { this.error = error.message; }
);
}
}
2. 批量更新 #
javascript
class Store {
async updateMultiple(items) {
const results = await Promise.all(
items.map(item => fetch(`/api/items/${item.id}`, {
method: 'PUT',
body: JSON.stringify(item)
}))
);
// 一次性更新所有数据
runInAction(() => {
results.forEach((response, index) => {
const item = this.items.find(i => i.id === items[index].id);
if (item) {
Object.assign(item, items[index]);
}
});
});
}
}
3. 取消请求 #
javascript
class Store {
abortController = null;
async fetchData() {
// 取消之前的请求
if (this.abortController) {
this.abortController.abort();
}
this.abortController = new AbortController();
this.loading = true;
try {
const response = await fetch('/api/data', {
signal: this.abortController.signal
});
const data = await response.json();
runInAction(() => {
this.data = data;
this.loading = false;
});
} catch (error) {
if (error.name !== 'AbortError') {
runInAction(() => {
this.error = error.message;
this.loading = false;
});
}
} finally {
this.abortController = null;
}
}
}
总结 #
| 方法 | 适用场景 | 特点 |
|---|---|---|
runInAction |
简单异步操作 | 灵活,可嵌套 |
flow |
复杂异步流程 | 可读性好,支持取消 |
选择建议:
- 简单异步操作:使用
runInAction - 复杂异步流程:使用
flow - 需要取消功能:使用
flow
继续学习 Computed计算值,了解如何创建派生状态。
最后更新:2026-03-28