Mobx vs Recoil application

3,898 阅读4分钟

前言

当前前端比较流行的框架,react、vue等,相关配套工具也是比较完善,对于大型项目而言,合理组织数据管理,对项目整体质量与开发效率密不可分,比如

  • vue: vuex
  • react:redux、mobx(v4/5、v6)、recoil

在这里,暂不细致比较 redux,就个人而言,不选择 redux 有以下几点

  • 首先 Redux 这一套的机制,React Hooks 本身就已经实现,useReducer、useContext,没有必要
  • 重要的一点,redux 数据流机制是从上至下传递,与当前流行的 “即插即用”的设计原则不符,并且开发成本较 mobx 与 recoil 要高(umi、dva除外,umi学习和维护成本较高、dva不太灵活,它们有些限制)

整体对比

  • mobxjs/mobx (v6):github.com/mobxjs/mobx
  • facebook/Recoil :github.com/facebookexp… | 对比项 | mobx | recoil | |:--|:--|:--| |Github Star 数|23.1k|11.4k| |React hooks 支持|mobx v6 + mobx-react
    - mobx 是可观察数据对象工具,可以在react、vue、js项目中使用
    - mobx-react 是将可观察对象与react关联起来的工具|无需额外辅助工具,天然支持| |UI与逻辑 解耦|支持
    - react 调用 store的action,逻辑在store中处理|不支持
    - react调用 recoil的 useRecoilState API,在组件中实现业务逻辑,比如:
    const [amount, setAmount] = useRecoilState(amountState)| |单例与多例|- export default new Store() 内部实例化,为单例
    - export default Store,引用处实例化,为多例
    通过程序控制,较为灵活,便于定制化|每一个atom 和 selector 都是单例| |数据流向(单项目)|- globalStore => Root => React.FC
    - pageStore => React.FC|atom[ => selector] => React.FC| |状态感应|when,reaction|不支持| |store间交互|支持|不支持| |常用 API 数(个)|5+|6+| |学习成本|较低|较低| |理解成本|低|中| |开发体验|友好|一般,甚至有点麻烦,不适合大型项目|

基本使用对比

mobx

  • 以 testStore 为例
import { makeAutoObservable, runInAction } from 'mobx';

class TestStore {
  amount = 1; // observable state
  data; // observable state

  constructor() {
    // 自动监听所有属性
    makeAutoObservable(this);
  }
  
  // computed
  get price(): string {
    return `$${this.amount * 100}`;
  }
  
  // action
  increment(): void {
    this.amount++;
  }
  
  // async action,
  async asyncAction(id: string): Promise<void> {
    try {
      const data = await geAsyncData(id);
      // 异步action中,改变状态时,外层要包裹 runInAction(() => {/** ...*/})
      runInAction(() => {
        this.data = data;
      });
    } catch (e) {
      throw new Error(e);
    }
  }
}

export default new TestStore();
  • Use In React:需结合 mobx-react
import { FC } from 'react';
import { observer } from 'mobx-react';
import store from './testStore';

const Test: FC = () => {
  return (
    <div>
      <div>count: {store.amount}</div>
      <div>price: {store.price}</div>
      <button type="button" onClick={() => store.increment()}>add +1</button>
      <button type="button" onClick={() => store.asyncAction(1)}>异步请求</button>
    </div>
  );
};

// 监听 Component
export default observer(Test);

recoil

  • recoil 的数据都是粒子,铺平的,
import { atom, selector } from 'recoil';

// state
export const amountState = atom({
  key: 'amountState',
  default: 0,
});

// computed
export const priceState = selector({
  key: 'priceState',
  get: ({ get }) => {
    return `$${get(amountState) * 100}`; ;
  }
});

// async state request
export const asyncTestData = selector({
  key: 'asyncTestData',
  get: async ({ get }) => {
    return await geAsyncData();
  }
});

// what's the fuck,if you want request async data with params in recoil-store, you must use another API "selectorFamily"

// async state request with params
export const asyncTestDataWithParams = selectorFamily({
  key: 'asyncTestDataWithParams',
  get: id => async ({ get }) => {
    return await geAsyncData(id);
  }
});
  • Use In React
import { FC } from 'react';
import { useRecoilState, useRecoilValue } from 'mobx-react';
import { amountState, priceState, asyncTestDataWithParams } from './testStore';

const Test: FC = () => {
  const [amount, setAmount] = useRecoilState(amountState);
  const price = useRecoilValue(priceState);
  
  const getData = () => {
  	return userRecoilValue(asyncTestDataWithParams(1));
  }
  
  return (
    <div>
      <div>count: {amount}</div>
      <div>price: {price}</div>
      <button type="button" onClick={() => setAmount(amount + 1)}>add +1</button>
      <button type="button" onClick={() => getData()}>异步请求</button>
    </div>
  );
};

// 监听 Component
export default observer(Test);

状态感应

  • 状态感应:当AStore的某个可观察数据达到条件时,会自动触发本store的其他动作 或 触发其他store的动作,程序描述就是 when(A.isReady === true) => B.action()
  • recoil 粒子说,不存在此场景
  • mobx 支持状态感应场景
class TestStore {
  isReady = false;
  count = 0;

  constructor() {
    makeAutoObservable(this);
    // 监听isReady,当 isReady 变为 true 时,执行 doSomething(一次性行为)
    when(() = this.isReady, () => this.doSomething());
    // 监听amount,当 amount 每次变化后,都会输出 value, previousValue
    reaction(
      () => this.amount,
      (value, previousValue) => {
        console.log(value, previousValue);
      }
    );
  }
  
  doReady(): void {
    this.isReady = true;
  }
  
  doSomething(): void {
    ...
  }
  
  increment(): void {
    this.amount++;
  }  
}

Store间交互

  • recoil:不支持
  • mobx:基于 store实例的单例模式,且 store之间可以互相引用和调用,可以实现通过store来控制数据流的初始化控制
import stores from './installStores';
class ScheduleStore {
  isRunning = false;
  constructor() {
    makeAutoObservable(this);
  }
  async run(): Promise<void> {
    try {
      // 用户信息初始化
      await stores.userStore.init();
      await stores.testStore.init();
      runInAction(() => {
        this.isRunning = true;
      });
    } catch (e) {
      console.log(e);
    }
  }
}

Store形式

  • mobx: 一个 store 为一个class,便于功能扩展:继承 class、实现接口
  • Recoli:一个个粒子store,无法扩展,承接高级用法

尾声

mobx 与 recoil 基本对比分析已经OK、总之,各有各的特点,都是非常优秀数据管理工具,在技术选型时,还需参考项目实际应用场景和开发成本,希望本文能对你有所有帮助。

如果想继续了解 mobx ,可以阅读 react hooks + mobx usage of summary

参考文献

❤️ 加入我们

字节跳动 · 幸福里团队

Nice Leader:高级技术专家、掘金知名专栏作者、Flutter中文网社区创办者、Flutter中文社区开源项目发起人、Github社区知名开发者,是dio、fly、dsBridge等多个知名开源项目作者

期待您的加入,一起用技术改变生活!!!

招聘链接: job.toutiao.com/s/JHjRX8B