MobX实战指南:状态管理的高效写法与常见陷阱

a'ゞ姗姗 框架 阅读 845
赞 15 收藏
二维码
手机扫码查看
反馈

先看效果,再看代码

我第一次用 MobX 是在重构一个老项目的状态管理模块。当时 Redux 写得又臭又长,改个状态要改七八个文件,烦死了。后来一咬牙切了 MobX,结果发现:真香。

MobX实战指南:状态管理的高效写法与常见陷阱

核心就一点:你不用管“怎么变”,只管“变成什么样”。MobX 会自动追踪哪些组件用了哪些数据,数据一变,用到的地方自动更新。亲测有效,比手动写 shouldComponentUpdate 省心多了。

来看个最简单的例子:

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

class CounterStore {
  count = 0;

  constructor() {
    makeAutoObservable(this);
  }

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

const counterStore = new CounterStore();

function Counter() {
  return (
    <div>
      <p>{counterStore.count}</p>
      <button onClick={() => counterStore.increment()}>
        +1
      </button>
    </div>
  );
}

export default observer(Counter);

注意:observer 包裹组件,makeAutoObservable 让类变成响应式。就这么简单,别被文档里一堆 @observableaction 的装饰器吓到,现在官方推荐用 makeAutoObservable,更清爽。

这个场景最好用:表单状态管理

表单是前端最头疼的场景之一,尤其是动态表单、嵌套字段、联动校验。我之前用 Redux 写表单,光 reducer 就写了 200 行,还经常漏掉某个字段的更新逻辑。

换成 MobX 后,直接把表单状态塞进 store,每个字段都是可观察的,UI 自动同步。而且因为是 mutable(可变)的,写起来像操作普通对象,不用到处 spread。

class FormStore {
  fields = {
    name: '',
    email: '',
    phone: ''
  };

  errors = {};

  constructor() {
    makeAutoObservable(this);
  }

  updateField(field, value) {
    this.fields[field] = value;
    // 清除对应字段的错误
    delete this.errors[field];
  }

  validate() {
    this.errors = {};
    if (!this.fields.name) this.errors.name = '姓名不能为空';
    if (!this.fields.email) this.errors.email = '邮箱不能为空';
    return Object.keys(this.errors).length === 0;
  }

  submit() {
    if (this.validate()) {
      console.log('提交数据:', this.fields);
      // 实际项目中这里会发请求
    }
  }
}

配合 React 组件,几乎不用写 useEffect 监听变化,store 一变,UI 就跟着变。我试过在复杂表单里嵌套三层结构,照样丝滑。建议直接用这种方式,比那些 formik + yup 的组合轻量太多。

踩坑提醒:这三点一定注意

虽然 MobX 好用,但有几个坑我踩过好几次,必须提醒你:

  • 不要在 render 里创建新对象或数组:MobX 通过引用追踪依赖。如果你在组件里写 const list = store.items.map(...),每次 render 都会生成新数组,导致 observer 误判依赖变化,疯狂重渲染。正确做法是把计算逻辑移到 store 里,用 computed 缓存。
  • 异步操作记得用 action:虽然 makeAutoObservable 默认会把方法包装成 action,但如果你在异步回调里直接修改状态,比如:
    fetchData() {
          api.get('/data').then(res => {
            this.data = res; // 这里会报错!
          });
        }

    MobX 在严格模式下会禁止在非 action 中修改状态。解决办法是用 runInAction 包裹,或者显式声明为 action:

    import { runInAction } from 'mobx';
    
        fetchData() {
          api.get('/data').then(res => {
            runInAction(() => {
              this.data = res;
            });
          });
        }
  • 避免在 store 里存 DOM 或函数引用:MobX 会尝试追踪所有属性,如果 store 里有 onSubmit 回调函数,而这个函数在每次 render 时都重新定义(比如父组件传下来的),那 observer 会认为依赖变了,导致不必要的重渲染。解决方案是把回调抽离,或者用 useCallback 固定引用。

高级技巧:computed 和 reaction 的妙用

很多人只知道 observable 和 observer,其实 computedreaction 才是 MobX 的精华。

computed 适合做派生数据,比如过滤列表、计算总价。它会自动缓存,只有依赖变了才重新计算:

class TodoStore {
  todos = [];

  constructor() {
    makeAutoObservable(this, {
      completedCount: computed,
      activeTodos: computed
    });
  }

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

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

reaction 适合做副作用,比如监听某个状态变化后自动保存到 localStorage:

import { reaction } from 'mobx';

// 在 store 初始化后调用
reaction(
  () => this.todos,
  (todos) => {
    localStorage.setItem('todos', JSON.stringify(todos));
  },
  { fireImmediately: true }
);

注意:reaction 默认不会立即执行,除非你加 fireImmediately: true。我一开始漏了这个,调试半天发现初始化数据没存进去。

还有一个冷门但实用的:when。它会在条件满足时执行一次回调,适合做“等数据加载完再干某事”的场景:

when(
  () => this.isLoading === false && this.data.length > 0,
  () => {
    console.log('数据准备好了');
    // 比如初始化图表
  }
);

和 Context 配合?其实没必要

很多人习惯把 store 丢进 React Context,然后用 useContext 取。但 MobX 官方其实不推荐这么做——因为 store 本身就是一个全局单例,直接 import 用就行,省去一层 Provider 包裹。

当然,如果你的项目需要 SSR(服务端渲染),或者要支持多个 store 实例(比如多租户),那还是得用 Context。但普通项目真没必要折腾。

我的做法是:在 stores/index.js 里统一导出所有 store 实例:

// stores/index.js
import CounterStore from './CounterStore';
import FormStore from './FormStore';

export const counterStore = new CounterStore();
export const formStore = new FormStore();

然后在组件里直接 import 用。简单粗暴,还少写一堆 Context 代码。

最后说两句

MobX 不是银弹,但在中小型项目里,它真的能让你少写 50% 的样板代码。如果你还在被 Redux 的 action/type/reducer 折磨,不妨试试 MobX。上手成本低,心智负担小,关键是——写起来爽。

以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多(比如和 React Native 配合、自定义 interceptor 等),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论