computed与autorun区别 #

computedautorun 都是 MobX 中响应式编程的重要组成部分,但它们有不同的用途和特点。理解它们的区别对于正确使用 MobX 至关重要。

核心区别 #

text
┌─────────────────────────────────────────────────────────────┐
│                    computed vs autorun                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  computed:                                                   │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐                 │
│  │  State  │ ── │computed │ ── │  Value  │                 │
│  └─────────┘    └─────────┘    └─────────┘                 │
│                      │                                       │
│                      ▼                                       │
│              返回值(可被其他代码使用)                        │
│                                                             │
│  autorun:                                                    │
│  ┌─────────┐    ┌─────────┐    ┌─────────────┐             │
│  │  State  │ ── │ autorun │ ── │ Side Effect │             │
│  └─────────┘    └─────────┘    └─────────────┘             │
│                      │                                       │
│                      ▼                                       │
│              副作用(不返回值)                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

对比表 #

特性 computed autorun
返回值 有(计算值)
缓存
触发时机 被访问时 立即执行
用途 派生数据 副作用
纯净性 必须是纯函数 可以有副作用

computed详解 #

特点 #

  1. 返回值:computed 总是返回一个值
  2. 缓存:只有依赖变化时才重新计算
  3. 惰性:只有被访问时才计算
  4. 纯净:应该是纯函数,没有副作用

使用场景 #

javascript
import { makeAutoObservable } from 'mobx';

class Store {
  firstName = 'John';
  lastName = 'Doe';

  constructor() {
    makeAutoObservable(this);
  }

  // 计算值 - 返回派生数据
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  // 计算值 - 用于显示
  get displayName() {
    return this.fullName.toUpperCase();
  }

  // 计算值 - 用于验证
  get isValid() {
    return this.firstName.length > 0 && this.lastName.length > 0;
  }
}

const store = new Store();

// 访问计算值
console.log(store.fullName);    // 'John Doe'
console.log(store.displayName); // 'JOHN DOE'
console.log(store.isValid);     // true

autorun详解 #

特点 #

  1. 无返回值:autorun 不返回值
  2. 无缓存:每次依赖变化都执行
  3. 立即执行:创建时立即执行一次
  4. 副作用:用于执行副作用

使用场景 #

javascript
import { makeAutoObservable, autorun } from 'mobx';

class Store {
  count = 0;

  constructor() {
    makeAutoObservable(this);
  }
}

const store = new Store();

// 自动运行 - 执行副作用
autorun(() => {
  console.log(`Count changed to: ${store.count}`);
  // 立即输出: Count changed to: 0
});

store.count = 1;  // 输出: Count changed to: 1
store.count = 2;  // 输出: Count changed to: 2

典型使用场景对比 #

场景1:派生数据 #

javascript
// ✅ 使用 computed - 派生数据
class TodoStore {
  todos = [];

  constructor() {
    makeAutoObservable(this);
  }

  get completedCount() {
    return this.todos.filter(t => t.completed).length;
  }

  get activeCount() {
    return this.todos.filter(t => !t.completed).length;
  }
}

// ❌ 不应该使用 autorun
autorun(() => {
  // 这不是派生数据,而是副作用
  store.completedCount = store.todos.filter(t => t.completed).length;
});

场景2:日志记录 #

javascript
// ✅ 使用 autorun - 日志副作用
autorun(() => {
  console.log(`State changed: ${JSON.stringify(toJS(store))}`);
});

// ❌ 不应该使用 computed
get logState() {
  console.log(`State changed: ${JSON.stringify(toJS(this))}`);
  return null;  // 这不是 computed 的正确用法
}

场景3:本地存储 #

javascript
// ✅ 使用 autorun - 保存到 localStorage
autorun(() => {
  localStorage.setItem('todos', JSON.stringify(toJS(store.todos)));
});

// ❌ 不应该使用 computed
get saveTodos() {
  localStorage.setItem('todos', JSON.stringify(toJS(this.todos)));
  return null;  // 副作用不应该在 computed 中
}

场景4:网络请求 #

javascript
// ✅ 使用 autorun - 触发网络请求
autorun(() => {
  if (store.searchQuery) {
    fetch(`/api/search?q=${store.searchQuery}`)
      .then(res => res.json())
      .then(data => runInAction(() => store.results = data));
  }
});

// ❌ 不应该使用 computed
get searchResults() {
  if (this.searchQuery) {
    // 不能在 computed 中进行异步操作
    fetch(`/api/search?q=${this.searchQuery}`);
  }
  return this.results;
}

组合使用 #

computed 和 autorun 可以组合使用:

javascript
import { makeAutoObservable, autorun } from 'mobx';

class Store {
  items = [];
  filter = 'all';

  constructor() {
    makeAutoObservable(this);

    // 使用 autorun 监听计算值变化
    autorun(() => {
      console.log(`Active items: ${this.activeItems.length}`);
    });
  }

  // 计算值
  get activeItems() {
    return this.items.filter(item => item.active);
  }

  get filteredItems() {
    switch (this.filter) {
      case 'active':
        return this.activeItems;
      default:
        return this.items;
    }
  }
}

完整示例 #

实时搜索 #

javascript
import { makeAutoObservable, autorun, runInAction } from 'mobx';

class SearchStore {
  query = '';
  results = [];
  loading = false;

  constructor() {
    makeAutoObservable(this);

    // 使用 autorun 实现防抖搜索
    let timeout;
    autorun(() => {
      if (timeout) clearTimeout(timeout);

      timeout = setTimeout(async () => {
        if (this.query.trim()) {
          this.loading = true;
          try {
            const response = await fetch(`/api/search?q=${this.query}`);
            const results = await response.json();
            runInAction(() => {
              this.results = results;
              this.loading = false;
            });
          } catch (error) {
            runInAction(() => {
              this.loading = false;
            });
          }
        } else {
          this.results = [];
        }
      }, 300);
    });
  }

  // 计算值 - 显示结果数量
  get resultCount() {
    return this.results.length;
  }

  // 计算值 - 是否有结果
  get hasResults() {
    return this.results.length > 0;
  }

  setQuery(query) {
    this.query = query;
  }
}

状态持久化 #

javascript
import { makeAutoObservable, autorun, toJS } from 'mobx';

class PersistentStore {
  user = null;
  settings = {};

  constructor() {
    makeAutoObservable(this);

    // 从 localStorage 恢复状态
    this.loadFromStorage();

    // 使用 autorun 自动保存
    autorun(() => {
      this.saveToStorage();
    });
  }

  // 计算值 - 是否已登录
  get isLoggedIn() {
    return this.user !== null;
  }

  // 计算值 - 用户显示名
  get displayName() {
    return this.user?.name || 'Guest';
  }

  // 副作用 - 保存到 localStorage
  saveToStorage() {
    const data = {
      user: toJS(this.user),
      settings: toJS(this.settings)
    };
    localStorage.setItem('app_state', JSON.stringify(data));
  }

  loadFromStorage() {
    const data = localStorage.getItem('app_state');
    if (data) {
      const parsed = JSON.parse(data);
      this.user = parsed.user;
      this.settings = parsed.settings;
    }
  }

  // Actions
  setUser(user) {
    this.user = user;
  }

  updateSettings(settings) {
    Object.assign(this.settings, settings);
  }

  logout() {
    this.user = null;
  }
}

选择指南 #

使用computed当: #

  • 需要从现有状态派生新值
  • 需要缓存计算结果
  • 需要在多个地方使用同一个派生值
  • 计算是纯函数
javascript
// ✅ computed
get fullName() {
  return `${this.firstName} ${this.lastName}`;
}

get filteredItems() {
  return this.items.filter(item => item.active);
}

get totalPrice() {
  return this.items.reduce((sum, item) => sum + item.price, 0);
}

使用autorun当: #

  • 需要执行副作用
  • 需要响应状态变化执行操作
  • 不需要返回值
  • 需要立即执行
javascript
// ✅ autorun
autorun(() => {
  document.title = `${store.unreadCount} unread messages`;
});

autorun(() => {
  localStorage.setItem('token', store.token);
});

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

总结 #

场景 使用
派生数据 computed
格式化显示 computed
数据验证 computed
统计计算 computed
日志记录 autorun
本地存储 autorun
DOM操作 autorun
网络请求 autorun

记住:computed 用于计算值,autorun 用于执行副作用

继续学习 Autorun自动运行,深入了解响应式副作用。

最后更新:2026-03-28