说说你对 MobX 的理解?
问题解析(面试官考察点)
面试官通过此问题主要考察:
- 对 MobX 核心概念的理解(响应式编程、观察者模式)
- MobX 与 Redux 的对比和选型能力
- MobX 的核心 API 使用
- MobX 的工作原理(依赖追踪、自动更新)
核心概念(基础知识点)
什么是 MobX
MobX 是一个简单、可扩展的状态管理库,它通过**函数式响应编程(FRP)**的方式使状态管理变得简单和可扩展。核心理念是:
任何可以从应用状态派生的内容都应该被自动派生。
MobX 核心概念
┌─────────────────────────────────────────────────────────────┐
│ MobX 核心概念关系图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ 观察 ┌─────────┐ │
│ │ State │◄───────────────│ Reaction │ │
│ │ (状态) │ │ (反应) │ │
│ └────┬────┘ └─────────┘ │
│ │ │
│ │ 派生 │
│ ▼ │
│ ┌─────────┐ │
│ │Computed │ │
│ │ (计算值) │ │
│ └─────────┘ │
│ │
│ ┌─────────┐ 修改 ┌─────────┐ │
│ │ Action │───────────────►│ State │ │
│ │ (动作) │ │ │ │
│ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
- State(状态): 驱动应用的数据
- Actions(动作): 修改状态的方法
- Computed Values(计算值): 从状态派生的值
- Reactions(反应): 状态变化时自动执行的副作用
详细解答(代码示例)
基础使用
1. 定义可观察状态 (observable)
import { observable, makeObservable } from "mobx";
// 类方式定义 Store
class TodoStore {
todos = [];
filter = "all"; // all, active, completed
constructor() {
makeObservable(this, {
todos: observable,
filter: observable
});
}
}
// 或者使用 makeAutoObservable
import { makeAutoObservable } from "mobx";
class TodoStore {
todos = [];
filter = "all";
constructor() {
makeAutoObservable(this);
}
}
// 对象方式定义
import { observable } from "mobx";
const store = observable({
todos: [],
filter: "all"
});
2. 定义计算属性 (computed)
import { computed, makeAutoObservable } from "mobx";
class TodoStore {
todos = [];
filter = "all";
constructor() {
makeAutoObservable(this);
}
// 计算属性:获取过滤后的待办事项
get filteredTodos() {
switch (this.filter) {
case "active":
return this.todos.filter(todo => !todo.completed);
case "completed":
return this.todos.filter(todo => todo.completed);
default:
return this.todos;
}
}
// 计算属性:获取待办事项数量
get remainingCount() {
return this.todos.filter(todo => !todo.completed).length;
}
// 计算属性:获取完成百分比
get completionRate() {
if (this.todos.length === 0) return 0;
const completed = this.todos.filter(todo => todo.completed).length;
return Math.round((completed / this.todos.length) * 100);
}
}
3. 定义动作 (action)
import { makeAutoObservable } from "mobx";
class TodoStore {
todos = [];
filter = "all";
constructor() {
makeAutoObservable(this);
}
// 添加待办事项
addTodo(text) {
this.todos.push({
id: Date.now(),
text,
completed: false
});
}
// 切换完成状态
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
}
// 删除待办事项
removeTodo(id) {
const index = this.todos.findIndex(t => t.id === id);
if (index > -1) {
this.todos.splice(index, 1);
}
}
// 设置过滤器
setFilter(filter) {
this.filter = filter;
}
// 批量操作
clearCompleted() {
this.todos = this.todos.filter(todo => !todo.completed);
}
}
在 React 中使用 MobX
使用 observer
import React from "react";
import { observer } from "mobx-react-lite";
// 函数组件使用 observer
const TodoList = observer(({ store }) => {
return (
<div>
<div className="stats">
<span>剩余: {store.remainingCount}</span>
<span>完成率: {store.completionRate}%</span>
</div>
<div className="filters">
{["all", "active", "completed"].map(f => (
<button
key={f}
className={store.filter === f ? "active" : ""}
onClick={() => store.setFilter(f)}
>
{f}
</button>
))}
</div>
<ul>
{store.filteredTodos.map(todo => (
<TodoItem key={todo.id} todo={todo} store={store} />
))}
</ul>
</div>
);
});
const TodoItem = observer(({ todo, store }) => {
return (
<li className={todo.completed ? "completed" : ""}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => store.toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => store.removeTodo(todo.id)}>删除</button>
</li>
);
});
// 类组件使用 observer
import { observer } from "mobx-react";
@observer
class TodoApp extends React.Component {
render() {
const { store } = this.props;
return (
<div>
<h1>Todo List</h1>
<TodoList store={store} />
</div>
);
}
}
使用 Context 传递 Store
// stores/TodoStore.js
import { makeAutoObservable } from "mobx";
class TodoStore {
todos = [];
constructor() {
makeAutoObservable(this);
}
addTodo(text) {
this.todos.push({ id: Date.now(), text, completed: false });
}
// ... 其他方法
}
export const todoStore = new TodoStore();
// contexts/StoreContext.js
import React from "react";
import { todoStore } from "../stores/TodoStore";
const StoreContext = React.createContext(todoStore);
export const StoreProvider = ({ children }) => (
<StoreContext.Provider value={todoStore}>
{children}
</StoreContext.Provider>
);
export const useStore = () => React.useContext(StoreContext);
// App.js
import { StoreProvider } from "./contexts/StoreContext";
import TodoApp from "./components/TodoApp";
function App() {
return (
<StoreProvider>
<TodoApp />
</StoreProvider>
);
}
// components/TodoApp.js
import { observer } from "mobx-react-lite";
import { useStore } from "../contexts/StoreContext";
const TodoApp = observer(() => {
const store = useStore();
const [text, setText] = React.useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
store.addTodo(text);
setText("");
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="添加待办事项"
/>
<button type="submit">添加</button>
</form>
<TodoList store={store} />
</div>
);
});
高级用法
异步 Action
import { makeAutoObservable, runInAction } from "mobx";
class UserStore {
users = [];
loading = false;
error = null;
constructor() {
makeAutoObservable(this);
}
// 方式1:使用 runInAction
async fetchUsers() {
this.loading = true;
this.error = null;
try {
const response = await fetch("/api/users");
const data = await response.json();
// 异步操作后需要使用 runInAction
runInAction(() => {
this.users = data;
this.loading = false;
});
} catch (error) {
runInAction(() => {
this.error = error.message;
this.loading = false;
});
}
}
// 方式2:使用 flow(推荐用于生成器函数)
*fetchUsersFlow() {
this.loading = true;
try {
const response = yield fetch("/api/users");
const data = yield response.json();
this.users = data;
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
}
}
响应式副作用 (reaction / autorun)
import { makeAutoObservable, autorun, reaction, when } from "mobx";
class TimerStore {
seconds = 0;
isRunning = false;
constructor() {
makeAutoObservable(this);
// autorun: 自动追踪依赖并在变化时执行
autorun(() => {
console.log(`当前秒数: ${this.seconds}`);
});
// reaction: 追踪特定数据的变化
reaction(
() => this.seconds,
(seconds, prevSeconds) => {
console.log(`秒数从 ${prevSeconds} 变为 ${seconds}`);
}
);
// when: 条件满足时执行一次
when(
() => this.seconds >= 60,
() => {
console.log("已到达60秒");
this.stop();
}
);
}
start() {
this.isRunning = true;
this.interval = setInterval(() => {
this.seconds++;
}, 1000);
}
stop() {
this.isRunning = false;
clearInterval(this.interval);
}
reset() {
this.seconds = 0;
}
}
深入理解(原理剖析)
MobX 响应式原理
┌─────────────────────────────────────────────────────────────┐
│ MobX 响应式系统架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Observable │◄──── 被观察的数据(state) │
│ └──────┬──────┘ │
│ │ │
│ │ 收集依赖 │
│ ▼ │
│ ┌─────────────┐ │
│ │ Dependency │◄──── 依赖图谱(谁依赖了哪些数据) │
│ └──────┬──────┘ │
│ │ │
│ │ 通知更新 │
│ ▼ │
│ ┌─────────────┐ │
│ │ Reaction │◄──── 观察者(组件、autorun 等) │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
依赖追踪机制
// MobX 依赖追踪简化原理
// 1. 使用 Proxy 或 Object.defineProperty 拦截属性访问
function createObservable(target) {
return new Proxy(target, {
get(obj, key) {
// 收集依赖
if (currentReaction) {
registerDependency(obj, key, currentReaction);
}
return obj[key];
},
set(obj, key, value) {
obj[key] = value;
// 触发更新
notifyChange(obj, key);
return true;
}
});
}
// 2. 追踪函数执行
function autorun(fn) {
const reaction = () => {
currentReaction = reaction;
fn();
currentReaction = null;
};
reaction();
}
// 3. 依赖收集和通知
const dependencyMap = new WeakMap();
function registerDependency(obj, key, reaction) {
if (!dependencyMap.has(obj)) {
dependencyMap.set(obj, new Map());
}
const keyMap = dependencyMap.get(obj);
if (!keyMap.has(key)) {
keyMap.set(key, new Set());
}
keyMap.get(key).add(reaction);
}
function notifyChange(obj, key) {
const keyMap = dependencyMap.get(obj);
if (keyMap && keyMap.has(key)) {
keyMap.get(key).forEach(reaction => reaction());
}
}
MobX vs Redux
| 特性 | MobX | Redux |
|---|---|---|
| 学习曲线 | 平缓 | 较陡峭 |
| 代码量 | 较少 | 较多 |
| 数据流 | 多向,灵活 | 单向,严格 |
| 调试工具 | mobx-devtools | Redux DevTools(强大) |
| 适用场景 | 中小型项目、快速开发 | 大型项目、复杂状态管理 |
| 不可变性 | 不需要(自动处理) | 必须手动保证 |
| 样板代码 | 少 | 多(Action、Reducer、Type) |
最佳实践
1. Store 组织结构
// stores/RootStore.js
import { TodoStore } from "./TodoStore";
import { UserStore } from "./UserStore";
import { UIStore } from "./UIStore";
export class RootStore {
constructor() {
this.todoStore = new TodoStore(this);
this.userStore = new UserStore(this);
this.uiStore = new UIStore(this);
}
}
// stores/TodoStore.js
import { makeAutoObservable } from "mobx";
export class TodoStore {
rootStore;
todos = [];
constructor(rootStore) {
this.rootStore = rootStore;
makeAutoObservable(this);
}
// 可以访问其他 store
get currentUserTodos() {
const userId = this.rootStore.userStore.currentUser?.id;
return this.todos.filter(todo => todo.userId === userId);
}
}
2. 严格模式
import { configure } from "mobx";
// 强制所有状态修改必须通过 action
configure({
enforceActions: "always"
});
// 可选值:
// "never" - 不检查
// "observed" - 只对被观察的状态检查
// "always" - 总是检查
3. 避免内存泄漏
import { useEffect } from "react";
import { autorun } from "mobx";
function MyComponent({ store }) {
useEffect(() => {
// 返回清理函数
const disposer = autorun(() => {
console.log(store.value);
});
return () => disposer();
}, [store]);
return <div>{store.value}</div>;
}
面试要点
-
MobX 是什么: 简单、可扩展的状态管理库,基于函数式响应编程
-
核心概念:
observable: 定义可观察状态computed: 计算属性,自动缓存action: 修改状态的方法reaction/autorun: 响应式副作用
-
工作原理:
- 使用 Proxy 或 Object.defineProperty 实现响应式
- 自动追踪依赖关系
- 状态变化时自动通知依赖者
-
与 Redux 对比:
- MobX 学习曲线更平缓,代码量更少
- MobX 自动处理不可变性
- Redux 更适合大型项目,调试工具更强大
-
使用注意:
- 异步操作需要使用 runInAction 或 flow
- 建议开启严格模式 enforceActions
- 组件使用 observer 包裹才能响应变化