背景
因为要做一个应用起来比拟难受的轮子,最近钻研了下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来进行对立的状态治理。
本文完结,感激浏览,欢送探讨交换。如有不当之处感激指出。
关注
欢送大家关注我的公众号[德莱问前端
],文章首发在公众号下面。
除每日进行社区精选文章收集外,还会不定时分享技术文章干货。
心愿能够一起学习,共同进步。