头图

How to select the state management library for React large-scale projects?

德莱问前端
中文

background

To make a wheel that is more comfortable to use, I recently studied React's state management library. Of course, it is limited to the use level, which is to choose which state management library to use from a comfortable point of view. Based on the level of popularity and usage of the state management library in the React community on Github, to select, and then there is this article. Regarding which we chose in the end, we will inform you at the end of the article.

The principles for selecting libraries are as follows:

  • Fully embrace typescript, so the selected library needs to be typescript support friendly
  • Since react introduced hooks in 16.8, the era of functional programming began, so don’t have classes.
  • It must be simple to use, not too complicated, it is very lightweight to use, and the focus is on comfort
  • Supports modularization, can be managed centrally, and has low atomicity
  • Support esmodule, because we will consider migrating to vite later, although it is still webpack

So far, I have looked at the number of stars and used by of several popular state management libraries on Github, as well as the weekly downloads on npm, which can explain the framework in some ways. There is a small possibility of inaccuracy in the popularity of, but to a large extent, it is helpful to the frame selection.

Library namegithub stargithub usednpm weekly downloads
mobx23.9k83.9k671,787
redux-toolkit5.9k83.2k755,564
recoil13.5k83.9k95,245
zustand9.4k7.2k104,682
rematch7.3k2.5k33,810
concent950651,263

In the above table, we are going to select the object next. Which one we like depends on the posture when using it, which is more comfortable.

mobx

mobx is a very good react state management library. There is no doubt about it, and on Github, its usage is also the first. The official document address is zh.mobx.js.org . The example given on the official website is very simple, and most official websites are also the same, but I don’t need a simple example, I need a complete project example, so I refer to a project antd-pro-mobx . mobx needs to be used with the mobx-react link library.

According to the recommendation of npm above, it is necessary to use the class + observe function package method, the latest version 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)

Starting from the beginning of a new project, you should not choose the old version of the library area to use. Generally, you will choose a stable new version for use. Regarding typescript, the source code is already written in typescript, but it is not available on the official website and npm. I haven't seen any clues about Typescript, maybe it hasn't been released yet.

Let's compare our principles and take a look at mobx:

  • Support typescript - NO
  • Use functional programming - NO
  • Comfortable to use - OK
  • Low atomicity problem - OK
  • Support esmodule - OK

Let’s stop here for the mobx part, and welcome to inform me of what’s wrong. It’s really talented and shallow, and I haven’t used such a popular state management library.

reduxjs/toolkit

, let’s call it that for the time being, 160f66d79de479 redux official state management library , cra template redux ( npx create-react-app --template redux ) comes with a state management library, cra template redux-ts ( npx create-react-app --template redux-typescript ) comes with a state management library. Maybe these two templates also cause the toolkit The number of downloads and usage of is very large. Because it is the official library of redux, it needs to be used with react-redux. We don't care about these for the time being, let's take a look at how to use it, and test it for yourself. If there is improper use, you can point it out.

// 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'),
);

The above is the code of the main entry file. You can see that this usage is fairly common, and it is in line with the usage of 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;

The above is the code of the store main file, which is actually a reasonable use method given by the official.

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

The above is the use of the actual module, which will produce a reducer. The only inelegant place is that extraReducers is in series, but it can also be passed in the form of objects, but the support in ts is not friendly enough, as follows:

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

You can see that the above method is replaced by an object, but you need to write the type declaration yourself in the function; and for the serial method, typescript has automatically deduced the parameter type corresponding to the function.

Let's compare our principles and take a look at the toolkit:

  • Support typescript - OK
  • Use functional programming - OK
  • Comfortable to use - OK, except for the chained use of builder
  • Low atomicity problem - OK
  • Support esmodule - OK

recoil

recoil, react official state management library , along with react17, the official website is recoiljs.org , in fact, through the official documentation, we can see that it almost completely follows the use of react hooks without any matching The connector can be directly and seamlessly connected with react. However, this actually leads to a relatively strong atomicity, a unified state management needs to be re-encapsulated, and the workload is not small. In terms of typescript, 0.3.0 began to support, the latest version so far is 0.3.1. For example, I will look at the official example

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}</>;
}

From the above, we can simply compare our principles:

  • Support typescript - OK, although not very powerful
  • Use 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 have seen it, but after looking at the example on npm, this library is still very useful & practical, and it is very comfortable to use. It does not provide many APIs, but it is simple enough to be able to Meet the demand. There is no separate official website, but the readme is written in sufficient detail, it can be regarded as an address npm zustand , let’s take a look at the example provided by the official 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 <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>
  );
}

You can see that all the data is wrapped using createStore, which can define any type, which can be a stats type such as count, or functions (including asynchronous functions), which is the most simplistic; in addition, zustand also provides some other The tool functions and middleware of, about how to use middleware and tool functions, etc., not much to say here, you can go to npm to have a look.

From the above, we can simply compare our principles:

  • Support typescript - OK, but there are fewer examples in the official website description
  • Use functional programming - OK
  • Comfortable to use - OK
  • Low atomicity problem-not high, not low, medium, may need to use middleware to wrap, expand the use
  • Support esmodule - OK

rematch

Rematch, because some projects are using this library, so I simply looked at it and used it. There are many examples on the official website, you can check it out: rematchjs.org . Typescript is not supported in v1. 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();
      dispatch.info.setInfo(res);
    }
  }
}

In v2, typescript support was added, but the use of the above method 1 was removed, and only the second method was retained. Specific examples can go to rematch typescript view. This usage is actually slightly similar to the redux-toolkit above, but it seems that the download volume of rematch has dropped a lot recently.

Rematch does a good job in the modular encapsulation part. It can manage all states in a unified manner, and then divide the functions according to models, which is more comfortable to use.

From the above and some examples on the official website, we can simply compare our principles:

  • Support typescript - OK
  • Use functional programming - OK
  • Comfortable to use - OK
  • Low atomicity problem - OK
  • Support esmodule - OK

concent

Concent, another implementation of the state manager, is very similar to Vue3's setup in use. Why is it talked about concent, because several projects are using concent, and they perform well. The official website concentjs .concent is very powerful, all kinds of black magic, if you want to use the concent, there will be a relatively large learning cost, that is, 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")
);

Complete examples and APIs can be viewed on the official website. It supports the mode of class decorator, the method of function setup, and the method of not using setup, which can all be satisfied. The problem is that there are more apis and higher learning costs.

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

  • Support typescript - OK
  • Use functional programming - OK
  • Comfortable to use - OK, it means higher learning cost
  • Low atomicity problem - OK, support modularization, unified state management
  • Support esmodule - OK

in conclusion

So far, the usage and principle comparison of these several state management libraries has almost been completed. There may be improper use or wrong description, please point it out.

In the end we considered the following points:

  • Support typescript, use functional programming
  • Support modularization, unified state management
  • Learning costs are low, and state management libraries are more popular
  • Consider everyone’s opinions. It’s very objective. Choose a library that everyone is willing to use.

The final choice is the official redux toolkit for unified state management.

The end of this article, thanks for reading, welcome to discuss and exchange. Thank you for pointing out if there is any impropriety.

attention

Welcome everyone to pay attention to my [160f66d79deb3c Delai asked front-end], the article was first published on the official account.

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

Hope to learn together and make progress together.

阅读 3.2k
98 声望
584 粉丝
0 条评论
你知道吗?

98 声望
584 粉丝
宣传栏