返回首页

说说你对 MobX 的理解?

问题解析(面试官考察点)

面试官通过此问题主要考察:

  • 对 MobX 核心概念的理解(响应式编程、观察者模式)
  • MobX 与 Redux 的对比和选型能力
  • MobX 的核心 API 使用
  • MobX 的工作原理(依赖追踪、自动更新)

核心概念(基础知识点)

什么是 MobX

MobX 是一个简单、可扩展的状态管理库,它通过**函数式响应编程(FRP)**的方式使状态管理变得简单和可扩展。核心理念是:

任何可以从应用状态派生的内容都应该被自动派生。

MobX 核心概念

┌─────────────────────────────────────────────────────────────┐
│                    MobX 核心概念关系图                       │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   ┌─────────┐      观察       ┌─────────┐                   │
│   │  State  │◄───────────────│ Reaction │                   │
│   │  (状态) │                │ (反应)   │                   │
│   └────┬────┘                └─────────┘                   │
│        │                                                     │
│        │ 派生                                                │
│        ▼                                                     │
│   ┌─────────┐                                               │
│   │Computed │                                               │
│   │ (计算值) │                                               │
│   └─────────┘                                               │
│                                                              │
│   ┌─────────┐      修改       ┌─────────┐                   │
│   │  Action │───────────────►│  State  │                   │
│   │ (动作)  │                │         │                   │
│   └─────────┘                └─────────┘                   │
│                                                              │
└─────────────────────────────────────────────────────────────┘
  1. State(状态): 驱动应用的数据
  2. Actions(动作): 修改状态的方法
  3. Computed Values(计算值): 从状态派生的值
  4. 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>;
}

面试要点

  1. MobX 是什么: 简单、可扩展的状态管理库,基于函数式响应编程

  2. 核心概念:

    • observable: 定义可观察状态
    • computed: 计算属性,自动缓存
    • action: 修改状态的方法
    • reaction/autorun: 响应式副作用
  3. 工作原理:

    • 使用 Proxy 或 Object.defineProperty 实现响应式
    • 自动追踪依赖关系
    • 状态变化时自动通知依赖者
  4. 与 Redux 对比:

    • MobX 学习曲线更平缓,代码量更少
    • MobX 自动处理不可变性
    • Redux 更适合大型项目,调试工具更强大
  5. 使用注意:

    • 异步操作需要使用 runInAction 或 flow
    • 建议开启严格模式 enforceActions
    • 组件使用 observer 包裹才能响应变化