background

In order to create a comfortable wheel, we recently took a look at the React state management library. Of course, only at the use level, that is, choosing which state management library to use from a comfortable perspective. Based on the popularity and usage of the React state management library on Github, we selected the model and then came this post. As to which one we finally chose, we were told at the end of the post.

The principles for selecting libraries are as follows:

  • Fully embrace typescript, so selects libraries that are friendly to typescript support
  • Since react introduced hooks in 16.8, it started the era of functional programming, so no classes
  • It must be simple to use, not too complicated, lightweight to use, and the emphasis is on comfort
  • Supports modularity, can be centralized management, low atomicity
  • Support for ESModule, as migration to Vite will be considered later, although for now it is still WebPack

So far, I’ve looked at the number of stars and used by, as well as the weekly downloads on NPM for some of the more popular state management libraries on Github. However, to a large extent, the selection of the framework is helpful.

The library github star github used NPM weekly downloads
mobx 23.9 k. 83.9 k. 671787
redux-toolkit 5.9 k. 83.2 k. 755564
recoil 13.5 k. 83.9 k. 95245
zustand 9.4 k. 7.2 k. 104682
rematch 7.3 k. 2.5 k. 33810
concent 950 65 1263

In the table above, we are going to choose the next object, which one we like, we have to see the posture when using, which is more comfortable.

mobx

Mobx is a great react state management library, no doubt about it, and it’s one of the most used on Github. The official document address is zh.mobx.js.org. The examples on the website are very simple, as are most websites, but I didn’t need a simple example, I needed an example of a full project, so I referred to antD-pro-Mobx, a project on Github. Mobx needs to be used in conjunction with the Mobx-React link library.

Use the class + observe function package as recommended by NPM above.

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)
Copy the code

If you start a new project from scratch, you should not use the old version of the reservoir, but generally use the new version of the stable typescript. In terms of typescript, you can see that the source code is already written in typescript, but there is no evidence of typescript on the official website and NPM. Maybe it hasn’t been released yet.

Let’s compare our principles for mobx:

  • Support for typescript — NO
  • Using functional programming — NO
  • Comfortable to use — OK
  • Low atomicity problem — OK
  • Support esModule — OK

About the mobX part for the time being this, said wrong place welcome to inform, is really the talent is shallow, did not use so popular state management library.

reduxjs/toolkit

Redux (NPX create-React-app –template redux) is a crA template. The CRA template redux-TS (NPX create-React-app –template redux-typescript) comes with a state management library. Perhaps these two templates also contributed to the high number of downloads and usage of toolkit. Because it is the official redux library, it needs to be used with React-Redux. Regardless of these, we look at how to use, pro test ha, if there are improper use, you can point out.

// index.ts Main entry file
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'));Copy the code

Above is the code for the main entry file, so you can see that this is a fairly common way to use 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;
Copy the code

The above is the code for the store master file, which is actually the official fair use method.

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

const namespace: string = 'counter';

export interface ICounter {
  value: number;
}

const initialState: ICounter = {
  value: 1};// async Defines an asynchronous function
export const fetchInfo = createAsyncThunk(`The ${namespace}/fetchInfo`.async (value: number) = > {await sleep(1000);
  return {
    value: 9000 + value,
  };
});

// Create reducer with namespace
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;
Copy the code

The only inelegant feature is that extraReducers are called by concatenation. However, they can also be passed in the form of objects. However, the support in TS is not friendly enough.

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; ,}}});Copy the code

You can see it’s an object, but you have to write your own type declaration inside the function; In the serial way, typescript already automatically deduces the parameter types of functions.

Let’s compare our principles for the Toolkit:

  • Support for typescript — OK
  • Using functional programming — OK
  • Comfortable to use – OK, except for the chain use of Builder
  • Low atomicity problem — OK
  • Support esModule — OK

recoil

Recoil, the react the official state management library, as the react17, the official web site is recoiljs.org, actually through the official document, we can see that is almost completely follow the react the use of hooks, do not need to match any connector, can be directly with the react seamless connection. But this actually also leads to atomicity is relatively strong, the unified state management needs to carry on the second encapsulation, and the workload is not small. In typescript, support starts at 0.3.0, and the latest version so far is 0.3.1. Examples let me just look at the official examples

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);

    returntext.length; }});function CharacterCount() {
  const count = useRecoilValue(charCountState);

  return <>Character Count: {count}</>;
}
Copy the code

From the above, we can briefly compare our principles:

  • Support for typescript — OK, though not very strong
  • Using functional programming — OK
  • Comfortable to use — NO
  • Low atomicity problem — NO
  • Support esModule — OK

zustand

Zustand, this library, to be honest, is the first time I’ve seen it, but after looking at the example above, NPM, the library is very usable & very practical, very comfortable to use, not a lot of API provided, but it’s lean enough to meet the needs. There is no separate website, but the readMe is detailed enough that it is a sort of address NPM Zustand. Let’s take a look at the example provided on the website:

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  ) function Counter() { const count = useStore(state => state.count) return 

{count}

}`
; 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> ); } Copy the code

You can see that all data is wrapped in createStore, which can define any stats type, such as count, or functions (including asynchronous functions), for maximum simplicity. In addition, Zustand also provides some other tool functions and middleware, about how to use middleware and tool functions, you can go to NPM to see.

From the above, we can briefly compare our principles:

  • Supports typescript — OK, but there are few examples in the official description
  • Using functional programming — OK
  • Comfortable to use — OK
  • Low atomicity problem – not high, not low, medium, may need to use middleware to wrap, extend use
  • Support esModule — OK

rematch

Rematch, because some projects are using this library, so a brief look at use. Rematchjs.org.v1 does not support typescript. There are two ways to use it (for Effects).

Effects: {fetchInfo: async () => {const res = await requestInfo(); this.setState({ ... res; Effects: (dispatch) => {return {fetchInfo: async () => {const res = await requestInfo();} effects: (dispatch) => {return {fetchInfo: async () => {const res = await requestInfo(); dispatch.info.setInfo(res); }}}Copy the code

V2 adds typescript support, but removes the first option, leaving only the second. Examples can be found in rematch typescript. This is used in a slightly similar way to the redux-Toolkit above, but it seems that rematch has seen a drop in downloads recently.

Rematch does a good job of modularizing the package, managing all the states in one place, and then dividing the functionality by models is comfortable to use.

From the examples above and on our website, we can briefly compare our principles:

  • Support for typescript — OK
  • Using functional programming — OK
  • Comfortable to use — OK
  • Low atomicity problem — OK
  • Support esModule — OK

concent

Concent, another implementation of a state manager, has a lot in common with Vue3’s Setup. The reason we’re talking about Concent is because there are several projects that use Concent and they’re doing well. The official website Concentjs. concent has very powerful functions, including various dark magic. If you want to use Concent well, there will be a large learning cost, which means it may not be easy to use.

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"));Copy the code

The full example and API can be viewed on the official website. It supports the class decorator mode, the setup function mode, and the non-setup method. The problem is that there are many apis and learning costs are high.

Based on experience and the example above, we can briefly compare our principles again:

  • Support for typescript — OK
  • Using functional programming — OK
  • Comfortable to use – OK, is higher cost of learning
  • Low atomicity problem – OK, support modularity, state unified management
  • Support esModule — OK

conclusion

At this point, the comparison of usage and principles for these state management libraries is almost complete. You are welcome to point out any possible misuse or errors.

Finally, we considered the following points:

  • Supports typescript and uses functional programming
  • Supports modularity and unified state management
  • The learning cost is low, and the state management library is popular
  • Take people’s opinions, it’s objective, and choose a library that everyone wants to use

The official Redux Toolkit was chosen for unified status management.

The end of this article, thanks for reading, welcome to the discussion and communication. Thank you for pointing out any irregularities.

Focus on

Welcome to pay attention to my public number [delai asked front end], the first article in the public number above.

In addition to the daily collection of selected articles in the community, we will also share technical articles from time to time.

I hope we can study together and make progress together.