关于react.js:React大型项目状态管理库如何选型

7次阅读

共计 9179 个字符,预计需要花费 23 分钟才能阅读完成。

背景

因为要做一个应用起来比拟难受的轮子,最近钻研了下 React 的状态治理库,当然,仅限在应用层面,也就是用着难受的角度来抉择到底应用哪个状态治理库。本着在 Github 下面看看 React 社区内状态治理库的风行水平和应用水平的层面,来进行选型,而后就有了这篇文章,对于咱们最初抉择了哪个,文章开端告知。

抉择库的准则如下:

  • 全面拥抱 typescript,so 抉择的库须要对 typescript 反对敌对
  • react 自从 16.8 引入 hooks,接下来就开始了函数式编程的时代,所以不要有 class 这玩意
  • 肯定要应用简略,不要太简单,应用起来很轻量,重点是难受
  • 反对模块化,能够集中管理,原子性低
  • 反对 esmodule,因为前面会思考迁徙到 vite,尽管当初还是 webpack

截止目前为止,在 Github 下面看了一下以后比拟风行的几个状态治理库的 star 数和 used by 的数量,以及 npm 下面的周下载量(weekly downloads),这能够从某些方面说明明该框架的受欢迎水平,也有很小的可能性不精确,不过很大水平上,对框架选型是有所帮忙的。

库名 github star github used npm 周下载量
mobx 23.9k 83.9k 671,787
redux-toolkit 5.9k 83.2k 755,564
recoil 13.5k 83.9k 95,245
zustand 9.4k 7.2k 104,682
rematch 7.3k 2.5k 33,810
concent 950 65 1,263

下面表格中,就是咱们接下来要进行筛选的对象,到底中意哪个,还得看看应用起来的时候的姿态,哪个更加难受。

mobx

mobx 是一个十分优良的 react 状态治理库,这毋容置疑,而且在 Github 下面,它的使用量也是做到了第一, 官网文档地址 zh.mobx.js.org。官网下面给的例子是非常简单的,大多数官网也都如此,可是我不须要简略的例子,我须要一个残缺的我的项目的例子,于是参考了 github 下面的一个我的项目 antd-pro-mobx。mobx 须要搭配 mobx-react 的连贯库一起来应用。

依照 npm 下面的举荐是要应用 class + observe 函数包裹的形式,最新版本 v6:

import React from "react"
import ReactDOM from "react-dom"
import {makeAutoObservable} from "mobx"
import {observer} from "mobx-react"

// Model the application state.
class Timer {
    secondsPassed = 0

    constructor() {makeAutoObservable(this)
    }

    increase() {this.secondsPassed += 1}

    reset() {this.secondsPassed = 0}
}

const myTimer = new Timer()

// Build a "user interface" that uses the observable state.
const TimerView = observer(({timer}) => (<button onClick={() => timer.reset()}>Seconds passed: {timer.secondsPassed}</button>
))

ReactDOM.render(<TimerView timer={myTimer} />, document.body)

// Update the 'Seconds passed: X' text every second.
setInterval(() => {myTimer.increase()
}, 1000)

新我的项目的从头开始,应该不会抉择老版本的库区应用,个别会抉择稳固的新版本的进行应用,对于 typescript 方面, 看源码是曾经在应用 typescript 来编写了,不过在官网和 npm 下面并没有看到 typescript 的蛛丝马迹,可能是还没发版吧。

咱们比照咱们的准则看下对于 mobx:

  • 反对 typescript — NO
  • 应用函数式编程 — NO
  • 应用难受 — OK
  • 原子性低问题 — OK
  • 反对 esmodule — OK

对于 mobx 局部就暂且到这,说的不对的中央欢送告知,的确是满腹经纶,没怎么用过这么风行的状态治理库。

reduxjs/toolkit

toolkit,暂且这么叫吧,redux 官网状态治理库 ,cra 模板 redux(npx create-react-app --template redux) 自带状态治理库,cra 模板 redux-ts(npx create-react-app --template redux-typescript)自带状态治理库. 可能这两个模板也导致了 toolkit 的下载量和使用量十分大。也因为是 redux 官网库的起因,须要搭配 react-redux 来搭配应用。这些咱们暂且不论,咱们看下如何应用,亲测哈,如有使用不当,能够指出。

// index.ts 主入口文件
import React from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {RootStore} from 'modules/store';
import {fetchInfo} from 'modules/counter';

function App(props: any) {const count = useSelector((state: RootStore) =>  state.counter.value);
  const dispatch = useDispatch();
  return (
    <div>
      hello home
      <hr/>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(fetchInfo(2234))}
        >
          fetchInfo
        </button>
        <div>
          <span>{count}</span>
        </div>
    </div>
  );
};

ReactDOM.render(
  <App/>,
  document.getElementById('root'),
);

下面是主入口文件的代码,能够看到这个应用形式还算是比拟广泛,合乎 redux 的应用形式。

// modules/store.ts
import {configureStore, combineReducers} from '@reduxjs/toolkit';
import counter from './counter';
import {TypedUseSelectorHook, useSelector, useDispatch} from 'react-redux';

const reducer = combineReducers({counter,});

const store = configureStore({reducer,});

export type RootStore = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootStore> = useSelector;
export default store;

下面是 store 主文件的代码,这其实也是官网给出的正当应用形式。

// modules/counter.ts
import {createAsyncThunk, createSlice} from '@reduxjs/toolkit';

const namespace: string = 'counter';

export interface ICounter {value: number;}

const initialState: ICounter = {value: 1,};

// async 异步函数定义
export const fetchInfo = createAsyncThunk(`${namespace}/fetchInfo`, async (value: number) => {await sleep(1000);
  return {value: 9000 + value,};
});

// 创立带有命名空间的 reducer
const counterSlice = createSlice({
  name: namespace,
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchInfo.pending, (state, action) => {state.value = 1000;})
      .addCase(fetchInfo.fulfilled, (state, {payload}) => {state.value = payload.value;})
      .addCase(fetchInfo.rejected, (state, {payload}) => {state.value = 500;});
  },
});

const {reducer} = counterSlice;
export default reducer;

下面是在理论 module 的应用,会产出一个 reducer,惟一不优雅的中央在于 extraReducers 的调用形式采纳了串联的形式,不过还能够通过对象的模式进行传递,不过在 ts 中反对不够敌对, 如下:

const counterSlice = createSlice({
  name: namespace,
  initialState,
  reducers: {},
  extraReducers: {[fetchInfo.pending.type]: (state: Draft<ICounter>, action: PayloadAction<ICounter>) => {state.value = 1000;},
    [fetchInfo.pending.type]: (state: Draft<ICounter>, { payload}: PayloadAction<ICounter>) => {state.value = payload.value;},
    [fetchInfo.pending.type]: (state: Draft<ICounter>, action: PayloadAction<ICounter>) => {state.value = 500;},
  },
});

能够看到下面换成了对象的形式,不过在函数外面须要本人去写好类型申明;而串行的形式,typescript 曾经主动推导出了函数所对应的参数类型。

咱们比照咱们的准则看下对于 toolkit:

  • 反对 typescript — OK
  • 应用函数式编程 — OK
  • 应用难受 — OK,除了 builder 的链式应用形式
  • 原子性低问题 — OK
  • 反对 esmodule — OK

recoil

recoil,react 官网状态治理库,随着 react17 而来,官网网址为 recoiljs.org, 其实透过官网文档,咱们能够看到差不多是齐全遵循了 react hooks 的应用形式,不须要搭配任何连接器,能够与 react 间接无缝连贯。不过这其实也导致了原子性比拟强,对立的状态治理须要对其进行二次封装,而且工作量不小。在 typescript 方面,0.3.0 开始反对,以后为止最新的版本是 0.3.1。例子我就看下官网的例子

import React from 'react';
import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
} from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
}

const textState = atom({key: 'textState', // unique ID (with respect to other atoms/selectors)
  default: '', // default value (aka initial value)
});

function CharacterCounter() {
  return (
    <div>
      <TextInput />
      <CharacterCount />
    </div>
  );
}

function TextInput() {const [text, setText] = useRecoilState(textState);

  const onChange = (event) => {setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
}

const charCountState = selector({key: 'charCountState', // unique ID (with respect to other atoms/selectors)
  get: ({get}) => {const text = get(textState);

    return text.length;
  },
});

function CharacterCount() {const count = useRecoilValue(charCountState);

  return <>Character Count: {count}</>;
}

由下面,咱们能够简略再比照下咱们的准则:

  • 反对 typescript — OK,尽管力度不是很大
  • 应用函数式编程 — OK
  • 应用难受 — NO
  • 原子性低问题 — NO
  • 反对 esmodule — OK

zustand

zustand,这个库,说实话,是第一次看到,不过看了 npm 下面的例子,这个库还是很好用 & 很实用的,应用起来很难受,提供的 api 不是很多,然而够精简,可能满足需要。没有独自的官网,不过 readme 写的足够具体,算是个地址吧 npm zustand, 咱们来看下官网提供的例子:

import React from "react";
import create from "zustand";
import PrismCode from "react-prism";
import "prismjs";
import "prismjs/components/prism-jsx.min";
import "prismjs/themes/prism-okaidia.css";

const sleep = (time = 1000) => new Promise((r) => setTimeout(r, time));
const code = `import create from 'zustand'

const useStore = create(set => ({
  count: 1,
  inc: () => set(state => ({ count: state.count + 1})),
}))

function Controls() {const inc = useStore(state => state.inc)
  return <button onClick={inc}>one up</button>
)

function Counter() {const count = useStore(state => state.count)
  return <h1>{count}</h1>  
}`;

const useStore = create((set) => ({
  count: 1,
  inc: () => set((state) => ({count: state.count + 1})),
  sleep: async () => {await sleep(2000);
    set((state) => ({count: state.count + 30}));
  }
}));

function Counter() {const { count, inc, sleep} = useStore();
  return (
    <div class="counter">
      <span>{count}</span>
      <button onClick={inc}>one up</button>
      <button onClick={sleep}>30 up</button>
    </div>
  );
}

export default function App() {
  return (
    <div class="main">
      <div class="code">
        <div class="code-container">
          <PrismCode className="language-jsx" children={code} />
          <Counter />
        </div>
      </div>
    </div>
  );
}

能够看到所有的数据应用的 createStore 进行包裹,外面能够定义任意类型,能够是 count 的这样的 stats 类型,也能够应用函数(包含异步函数),做到了最简单化;另外 zustand 还提供了一些其余的工具函数和中间件,对于中间件和工具函数等的如何应用,此处就不多说了,能够去 npm 看看.

由下面,咱们能够简略再比照下咱们的准则:

  • 反对 typescript — OK, 然而官网形容外面的例子比拟少
  • 应用函数式编程 — OK
  • 应用难受 — OK
  • 原子性低问题 — 不高不低,中等,可能须要应用到中间件来包裹,扩大应用
  • 反对 esmodule — OK

rematch

rematch, 因为有局部我的项目在应用这个库,所以简略看了下应用。官网下面有很多例子,能够去看看: rematchjs.org. v1 的时候是不反对 typescript 的,应用上有两种形式(对于 effects)

// 形式一
effects: {fetchInfo: async () => {const res = await requestInfo();
    this.setState({...res;})
  }
}
// 形式二
effects: (dispatch) => {
  return {fetchInfo: async () => {const res = await requestInfo();
      dispatch.info.setInfo(res);
    }
  }
}

v2 的时候是减少了 typescript 的反对,不过却去掉了下面形式一的应用形式,只保留了第二种。具体例子能够返回 rematch typescript 查看。这个应用形式其实与下面的 redux-toolkit 略微有点类似,不过如同 rematch 最近下载量降落了不少。

rematch 在模块化封装局部做的很好,可能对所有的状态进行对立治理,而后能够按 models 进行划分性能,应用起来比拟难受。

由下面以及官网下面的一些例子,咱们能够简略再比照下咱们的准则:

  • 反对 typescript — OK
  • 应用函数式编程 — OK
  • 应用难受 — OK
  • 原子性低问题 — OK
  • 反对 esmodule — OK

concent

concent,另外一种状态管理器的实现形式,在应用上与 Vue3 的 setup 有很大相似之处。为什么谈判到 concent,因为有好几个我的项目在应用 concent,而且体现良好。官方网站 concentjs.concent 性能十分弱小,各种黑魔法,要想应用好 concent,会有比拟大的学习老本,也就是应用起来可能不会很简略。

import {StrictMode} from "react";
import ReactDOM from "react-dom";
import {run, useConcent} from "concent";

const sleep = (time = 1000) => new Promise((r) => setTimeout(r, time));

run({
  counter: {
    state: {value: ""},
    computed: {},
    lifecycle: {},
    reducer: {decrement: async (payload, moduleState) => {await sleep(1000);
        return {value: moduleState.value - 1};
      }
    }
  }
});

const setup = (ctx) => {ctx.effect(() => {
    ctx.setState({value: 1000});
  }, []);
  const increment = () => {console.log(1233);
    ctx.setState({value: ctx.state.value + 1});
  };
  return {increment};
};

export default function App() {const { state, settings, moduleReducer} = useConcent({
    module: "counter",
    setup
  });
  return (
    <div className="App">
      <h1>Hello Counter : {state.value}</h1>
      <div>
        <button onClick={settings.increment}>click increment</button>
        <button onClick={moduleReducer.decrement}>click decrement</button>
      </div>
    </div>
  );
}

ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  document.getElementById("root")
);

残缺的例子和 api 能够返回官网查看,反对 class 装璜器的模式,也反对函数 setup 的形式,也有不应用 setup 的形式,都能满足;问题就是 api 较多,学习老本较高。

依据教训和下面的例子,咱们能够简略再比照下咱们的准则:

  • 反对 typescript — OK
  • 应用函数式编程 — OK
  • 应用难受 — OK,就是学习老本较高
  • 原子性低问题 — OK,反对模块化,状态对立治理
  • 反对 esmodule — OK

论断

至此,这几种状态治理库的应用形式和准则比照差不多曾经实现。可能有使用不当或者阐明谬误的中央,欢送指出。

最终咱们思考了以下几点:

  • 反对 typescript,应用函数式编程
  • 反对模块化,状态对立治理
  • 学习成本低,且状态治理库比拟风行
  • 思考大家的意见,这很主观,抉择一个大家都违心应用的库

最终抉择的是 redux 官网的 toolkit 来进行对立的状态治理。

本文完结,感激浏览,欢送探讨交换。如有不当之处感激指出。

关注

欢送大家关注我的公众号 [ 德莱问前端],文章首发在公众号下面。

除每日进行社区精选文章收集外,还会不定时分享技术文章干货。

心愿能够一起学习,共同进步。

正文完
 0