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