Observer观察者 #

observer 是 mobx-react-lite 提供的高阶组件,它让 React 组件能够自动响应 MobX 状态的变化。当组件中使用的可观察状态发生变化时,组件会自动重新渲染。

基本用法 #

简单示例 #

javascript
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';

// 创建 Store
class CounterStore {
  count = 0;

  constructor() {
    makeAutoObservable(this);
  }

  increment() {
    this.count++;
  }
}

const counterStore = new CounterStore();

// 使用 observer 包装组件
const Counter = observer(() => {
  return (
    <div>
      <p>Count: {counterStore.count}</p>
      <button onClick={() => counterStore.increment()}>+1</button>
    </div>
  );
});

export default Counter;

工作原理 #

text
┌─────────────────────────────────────────────────────────────┐
│                   observer 工作原理                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   1. 组件渲染时,observer 追踪所有使用的可观察状态            │
│                                                             │
│   2. 状态变化时,observer 触发组件重新渲染                    │
│                                                             │
│   3. 只有实际使用的状态变化才会触发更新                       │
│                                                             │
│   ┌──────────┐     ┌──────────┐     ┌──────────┐           │
│   │  Store   │ ──► │ observer │ ──► │ Component │           │
│   │ (状态)   │     │ (追踪)   │     │ (渲染)    │           │
│   └──────────┘     └──────────┘     └──────────┘           │
│        │                │                                   │
│        └────────────────┘                                   │
│           状态变化触发更新                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

observer的特点 #

1. 细粒度更新 #

只有组件实际使用的状态变化才会触发更新:

javascript
const UserCard = observer(({ user }) => {
  // 只追踪 user.name
  return <div>{user.name}</div>;
});

// 修改 name 会触发更新
user.name = 'New Name';

// 修改 age 不会触发更新(因为组件没有使用 age)
user.age = 30;

2. 自动追踪 #

自动追踪渲染过程中访问的所有可观察状态:

javascript
const TodoList = observer(() => {
  return (
    <div>
      <p>Total: {todoStore.totalCount}</p>
      <p>Active: {todoStore.activeCount}</p>
      <ul>
        {todoStore.filteredTodos.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
});
// 自动追踪: totalCount, activeCount, filteredTodos

3. 批量更新 #

多个状态修改只触发一次渲染:

javascript
const store = makeAutoObservable({
  a: 0,
  b: 0
});

const Component = observer(() => {
  return <div>{store.a} - {store.b}</div>;
});

// 批量修改,只渲染一次
store.a = 1;
store.b = 2;

使用方式 #

函数组件(推荐) #

javascript
import { observer } from 'mobx-react-lite';

const Counter = observer(() => {
  return <div>{counterStore.count}</div>;
});

带Props的组件 #

javascript
const TodoItem = observer(({ todo }) => {
  return (
    <div>
      <span>{todo.title}</span>
      <button onClick={() => todo.toggle()}>Toggle</button>
    </div>
  );
});

类组件 #

javascript
import { observer } from 'mobx-react';

class Counter extends React.Component {
  render() {
    return <div>{counterStore.count}</div>;
  }
}

export default observer(Counter);

完整示例 #

Todo应用 #

javascript
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';

// Store
class TodoStore {
  todos = [];
  filter = 'all';

  constructor() {
    makeAutoObservable(this);
  }

  addTodo(title) {
    this.todos.push({
      id: Date.now(),
      title,
      completed: false
    });
  }

  toggleTodo(id) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) todo.completed = !todo.completed;
  }

  setFilter(filter) {
    this.filter = filter;
  }

  get filteredTodos() {
    switch (this.filter) {
      case 'completed':
        return this.todos.filter(t => t.completed);
      case 'active':
        return this.todos.filter(t => !t.completed);
      default:
        return this.todos;
    }
  }

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

const todoStore = new TodoStore();

// 组件
const TodoItem = observer(({ todo }) => (
  <li
    style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
    onClick={() => todoStore.toggleTodo(todo.id)}
  >
    {todo.title}
  </li>
));

const TodoList = observer(() => (
  <ul>
    {todoStore.filteredTodos.map(todo => (
      <TodoItem key={todo.id} todo={todo} />
    ))}
  </ul>
));

const FilterButtons = observer(() => (
  <div>
    <button onClick={() => todoStore.setFilter('all')}>All</button>
    <button onClick={() => todoStore.setFilter('active')}>Active</button>
    <button onClick={() => todoStore.setFilter('completed')}>Completed</button>
  </div>
));

const AddTodo = () => {
  const [title, setTitle] = React.useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (title.trim()) {
      todoStore.addTodo(title);
      setTitle('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Add todo"
      />
      <button type="submit">Add</button>
    </form>
  );
};

const TodoApp = observer(() => (
  <div>
    <h1>Todo ({todoStore.activeCount} remaining)</h1>
    <AddTodo />
    <TodoList />
    <FilterButtons />
  </div>
));

export default TodoApp;

用户列表 #

javascript
import { makeAutoObservable, runInAction } from 'mobx';
import { observer } from 'mobx-react-lite';

class UserStore {
  users = [];
  loading = false;
  error = null;

  constructor() {
    makeAutoObservable(this);
  }

  async fetchUsers() {
    this.loading = true;
    try {
      const response = await fetch('/api/users');
      const users = await response.json();
      runInAction(() => {
        this.users = users;
        this.error = null;
      });
    } catch (error) {
      runInAction(() => {
        this.error = error.message;
      });
    } finally {
      runInAction(() => {
        this.loading = false;
      });
    }
  }
}

const userStore = new UserStore();

const UserList = observer(() => {
  if (userStore.loading) return <div>Loading...</div>;
  if (userStore.error) return <div>Error: {userStore.error}</div>;

  return (
    <ul>
      {userStore.users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
});

const UserApp = () => {
  React.useEffect(() => {
    userStore.fetchUsers();
  }, []);

  return (
    <div>
      <h1>Users</h1>
      <UserList />
    </div>
  );
};

export default UserApp;

性能优化 #

1. 组件拆分 #

javascript
// 不好:一个大组件
const BigComponent = observer(() => {
  return (
    <div>
      <Header user={userStore.user} />
      <Content items={itemStore.items} />
      <Footer count={counterStore.count} />
    </div>
  );
});

// 好:拆分成小组件
const Header = observer(() => (
  <header>{userStore.user.name}</header>
));

const Content = observer(() => (
  <main>{itemStore.items.map(...)}</main>
));

const Footer = observer(() => (
  <footer>{counterStore.count}</footer>
));

const BigComponent = () => (
  <div>
    <Header />
    <Content />
    <Footer />
  </div>
);

2. 避免不必要的追踪 #

javascript
// 不好:追踪整个对象
const UserCard = observer(({ user }) => (
  <div>{JSON.stringify(user)}</div>
));

// 好:只追踪需要的属性
const UserCard = observer(({ user }) => (
  <div>{user.name}</div>
));

3. 使用useMemo #

javascript
const ExpensiveList = observer(() => {
  const sortedItems = React.useMemo(
    () => [...itemStore.items].sort((a, b) => a.name.localeCompare(b.name)),
    [itemStore.items]
  );

  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
});

注意事项 #

1. 不要在observer外访问状态 #

javascript
// 不好:在 observer 外访问状态
const count = counterStore.count;

const Counter = observer(() => {
  return <div>{count}</div>;  // 不会响应变化
});

// 好:在 observer 内访问状态
const Counter = observer(() => {
  return <div>{counterStore.count}</div>;
});

2. 解构会失去响应性 #

javascript
// 不好:解构后失去响应性
const Counter = observer(() => {
  const { count } = counterStore;  // 失去响应性
  return <div>{count}</div>;
});

// 好:直接访问
const Counter = observer(() => {
  return <div>{counterStore.count}</div>;
});

3. 异步渲染中的状态访问 #

javascript
// 不好:异步访问状态
const BadComponent = observer(() => {
  const [data, setData] = React.useState(null);

  React.useEffect(() => {
    setTimeout(() => {
      setData(store.data);  // 不在渲染中,不会被追踪
    }, 1000);
  }, []);

  return <div>{data}</div>;
});

// 好:在渲染中访问状态
const GoodComponent = observer(() => {
  return <div>{store.data}</div>;
});

总结 #

observer 的核心要点:

  • 自动追踪:自动追踪渲染中使用的状态
  • 细粒度更新:只有使用的状态变化才更新
  • 批量更新:多个修改只触发一次渲染
  • 高性能:避免不必要的渲染

使用建议:

  • 所有使用 MobX 状态的组件都用 observer 包装
  • 组件拆分,提高更新精度
  • 在 observer 内访问状态
  • 避免解构可观察对象

继续学习 Observer组件,了解更细粒度的响应式控制。

最后更新:2026-03-28