preface

Hello, I’m Xu Zhu.

React is a uI-focused library. Unlike Vue, Angular and other frameworks, React’s various state management solutions are always in full bloom. In addition to popular libraries such as Redux, Mobx, Recoil, Zustand, etc., React has also come to V17. Concepts and applications related to state management such as useState, useReducer, and useContext have gradually gained popular support.

As an ordinary programmer, the primary concern is how to quickly pick a mature and comfortable wheel to solve the project at hand. Since the technology stack used by our team was React, we spent some time working on the React state management library. Of course, only at the usage level, where you choose which state management library to use from a comfortable Angle. See Github to see the popularity and usage of the React community’s state management library for a selection. You can continue to read this article, I think you should have the answer. If you’re still hesitant, leave a comment in the comments section for discussion.

Below is a simple shopping cart example to explain the use of the major state management libraries, including common apis. Github source code download address attached at the end of the article, if you have a drop of help, please also click a like or star support.

Demo renderings

Feature list

  • Add to shopping cart
  • Number of items in shopping cart
  • Select quantity of each item
  • Delete the item
  • Count the total price of selected goods

Popular state management library

Recoil

Github address: github.com/facebookexp…

Official website: recoiljs.org/zh-hans/

What is the Recoil

Recoil is a state management library developed by Facebook. The goal is to create a high performance state management library, and it can use the React scheduling mechanism, including supporting concurrent mode. Although it is still in the experimental stage, Facebook already uses some of it. Especially if you use React, try it out for small projects.

Using Recoil will create a data flow diagram for you, from Atom (shared state) to selector (pure function) to the React component. Atom is a unit of state to which a component can subscribe. Selector can change this state synchronously or asynchronously.

  • A decentralized mode for managing atomic states
  • Reading and writing separation
  • On-demand rendering
  • Derived from the cache

Commonly used API

  • RecoilRoot Status scope
  • Atom defines data decentrally
  • Selector Derives state, retrieving data asynchronously
  • SelectorFamily relies on external variables to read data
  • UseRecoilState reads and writes data
  • UseRecoilValue reads data
  • UseSetRecoilValue Updates writable status data

The directory structure

Code implementation

Install the Recoil

The most recommended way to Create a React project is to use the scaffolding Create React App.

npx create-react-app recoil-demo
Copy the code

To install the latest version of Recoil, run the following command:

npm install recoil
&
yarn add recoil
Copy the code

RecoilRoot initialization

As with Redux, global data flow management requires scoped RecoilRoot to exist, first importing RecoilRoot and placing it where the root component is (or other parent component), such as in an app.js file, as follows:

import { Routes, Route } from "react-router-dom";
import { RecoilRoot } from "recoil";

import Catalog from "./pages/cart"; / / main page
import Cart from "./pages/cart/Cart"; // Shopping cart list

function App() {
  return (
    <div className="App">
      <RecoilRoot>
        <Routes>
          <Route exact path="/" element={<Catalog />} / ><Route path="/cart" element={<Cart />} / ></Routes>
      </RecoilRoot>
    </div>
  );
}

export default App;
Copy the code

Atom defines state

Atom is recoil’s smallest unit of state. Atom means that a value can be read, written, and subscribed to, and it must have a key that is unique and immutable from other Atoms.

In the SRC directory, create the store folder, and then create the atoms. Js file as follows:

import { atom } from "recoil";

// Shopping cart status
export const cart = atom({
  key: "cart".// The key must be unique
  default: [].// Define default values
})
Copy the code

Create an index.js file in the Store folder to export the defined share status and methods as follows:

import { cart } from "./atoms";

export {
    cart
}
Copy the code

UseRecoilValue reads data

This hook is recommended when a component needs to read state without writing it.

In the cart list page SRC /pages/cart/ cart.js file, use Hooks to access useRecoilValue.

import React from "react";
import { Table } from "antd";
import { useRecoilValue } from "recoil";
import { cart } from ".. /.. /store";

function Cart() {
  const tableData = useRecoilValue(cart);

  return (
    <>
      <Table
        columns={columns}
        dataSource={tableData}
        size="middle"
      />
    </>
  );
}

export default Cart;
Copy the code

Then check the page effect, because the initial value is an empty array, so there is no data, as shown below:

UseRecoilState reads and writes data

This hook is recommended when the component needs both read and write states.

React useState() hook This API is similar to React useState() hook, except that useRecoilState uses Recoil state instead of useState() default. It returns a tuple of the current value of state and setter functions. A Setter function can take a new value, or it can be a updater function that takes a previous value.

import { useRecoilState } from "recoil";
import { cart } from "./atoms"; .// Add a single item
export const useIncrementItem = () = > {
  const [items, setItems] = useRecoilState(cart);

  return (product) = > {
    const { clone, index } = cloneIndex(items, product.id);

    if(index ! = = -1) {
      clone[index].amount += 1;
      setItems(clone);
    } else {
      setItems([...clone, { ...product, amount: 1}]); }}; };Copy the code

The Selector derived values

Selector represents a derived state, which is a transition of states. You can think of derived state as passing state to a pure function that modifies a given state in some way.

Add a fake data manually as follows:

import { atom } from "recoil";

export const cart = atom({
  key: "cart".// The key must be unique
  default: [{key: 1.id: 1.bookName: "Introduction to ES6 Standards".amount: 1.unitPrice: 88
         // Total unit price = unit price * quantity}].// Define default values
})
Copy the code

The total amount data is calculated using state management logic, including adding shopping cart counts, and creating a selectors. Js file in the Store folder as follows:

import { selector } from "recoil";
import { cart } from "./atoms";

export const cartState = selector({
  key: "cartState".get: ({get}) = > {
    const totalCost = get(cart).reduce((a, b) = > a + b.amount * b.unitPrice, 0); // Total amount
    const totalCartNum = get(cart).reduce((a, b) = > a + b.amount, 0); // Add to cart total

    return {
      totalCost,
      totalCartNum
    }
  }
})
Copy the code

To derive state using selector, useRecoilValue is required to get the data as follows:

import React from "react";
import { Table } from "antd";
import { useRecoilValue } from "recoil";
import { cartState } from ".. /.. /store"; .function Cart() {
  const { totalCost } = useRecoilValue(cartState);

  const footerDom = () = > {
    return (
      <>Total:<span className="total">Selections {totalCost}</span>
      </>); }; . }export default Cart;
Copy the code

Custom Hooks

Add, subtract, and delete items in the shopping cart using custom Hooks that read and write data using the useRecoilState API.

Create a new context.js file in the Store folder and implement the following code:

import { useRecoilState } from "recoil";
import { cart } from "./atoms";

// Copy the original data and obtain the index value that meets the condition
const cloneIndex = (items, id) = > ({
  clone: items.map((item) = > ({
    ...item,
  })),
  index: items.findIndex((item) = > item.id === id),
});

/ /
export const useIncrementItem = () = > {
  const [items, setItems] = useRecoilState(cart);

  return (product) = > {
    const { clone, index } = cloneIndex(items, product.id);

    if(index ! = = -1) {
      clone[index].amount += 1;
      setItems(clone);
    } else {
      setItems([...clone, { ...product, amount: 1}]); }}; };/ / decrement
export const useDecrementItem = () = > {
  const [items, setItems] = useRecoilState(cart);

  return (product) = > {
    const { clone, index } = cloneIndex(items, product.id);

    if(clone[index].amount ! = =1) {
      clone[index].amount -= 1; setItems(clone); }}}/ / delete
export const useRemoveItem = () = > {
  const [items, setItems] = useRecoilState(cart);

  return (product) = > {
    setItems(items.filter(item= > item.id !== product.id));
  }
}
Copy the code

Import the hooks from store/index.js as follows:

import { cart } from "./atoms";
import { cartState } from "./selectors";
import { useIncrementItem, useDecrementItem, useRemoveItem } from "./hooks";

export {
  cart,
  cartState,
  useIncrementItem,
  useDecrementItem,
  useRemoveItem
}
Copy the code

Open the packaged button component and import the store file directly as follows:

import React from "react";
import { Button } from "antd";
import PropTypes from "prop-types";
import { useIncrementItem, useDecrementItem, useRemoveItem } from ".. /.. /store";

function CartButtons(props) {
  const { item } = props;
  const increment = useIncrementItem();
  const decrement = useDecrementItem();
  const remove = useRemoveItem();

  return (
    <>
      <Button
        onClick={()= > decrement(item)}
        style={{ background: "#ddd", borderColor: "#ddd" }}
      >
        -
      </Button>
      <Button onClick={()= > increment(item)} type="primary">
        +
      </Button>
      <Button onClick={()= > remove(item)} type="primary" danger>
        x
      </Button>
    </>
  );
}

CartButtons.propTypes = {
  item: PropTypes.object, // PropTypes.object.isRequired
};

export default CartButtons;
Copy the code

Mastering these common apis allows you to enable Recoil in your project, which is more flexible in managing and organizing state than Redux. Recoil advocates more fine-grained control over state and derived data, which looks simple when written as a Demo, but is actually cumbersome when the code is large.

To learn more about other apis, go to the official documentation.

Redux Toolkit (RTK for short)

Github address: github.com/reduxjs/red…

Official website: Redux-Toolkit.js.org/

What is the Redux Toolkit

The Redux Toolkit is an official Redux upgrade tool. It simplifies the Redux application process and simplifies the Redux application process. The Redux Toolkit provides powerful state caching and state editing methods to further enhance state processing capabilities in Redux. The official Redux vision is that the Redux Toolkit will become the de facto standard method for writing Redux logic.

  • Simplify excessive template code in the Redux workflow
  • Simplify the creation and configuration of Store code that is independent of application logic
  • It integrates common Redux middleware without requiring developers to download and configure it separately

Commonly used API

  • ConfigureStore Creates a Redux Store instance
  • CreateSlice Creates a slice process
  • CreateAction Creates the actions function
  • CreateReducer Creates the reducer function
  • CreateAsyncThunk Handles asynchronous actions

The directory structure

Code implementation

Install Redux Toolkit

  1. The new project
# Redux + Plain JS template
npx create-react-app redux-toolkit-demo --template redux

# Redux + TypeScript template
npx create-react-app redux-toolkit-demo --template redux-typescript
Copy the code

Start the project

After the installation is successful, go to the project directory redux-Toolkit -demo and run the NPM start command to start the project, as shown in the following figure:

Directory structure, as shown below:

Specific source code examples to download to install view…

Install Redux DevTools

Open the Chrome Web App Store, search for the Redux DevTools development tool, and install the plugin directly, as shown below:

To refresh the interface, press F12 to see the effect, as shown below:

  1. Existing projects
npx create-react-app redux-toolkit-demo

npm install @reduxjs/toolkit react-redux
Copy the code

configureStore

ConfigureStore is an abstract wrapper around the createStore function of standard Redux, adding default values for a better development experience. Traditional Redux requires configuration of reducer, middleware, devTools, enhancers, and so on, and uses configureStore to directly encapsulate these defaults.

Create a store folder in the SRC directory, create an index.js file, and introduce configureStore to create an empty redux data as follows:

import { configureStore } from "@reduxjs/toolkit";

// The store already integrates with redux-thunk and Redux DevTools
export const store = configureStore({
  reducer: {},});Copy the code

In the root directory index.js file, import the React-Redux Provider and pass it the exported store as a prop. The code is as follows:

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { store } from "./store";
import App from "./App";
import "./index.less";

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>.document.getElementById("root"));Copy the code

createSlice

Take an initial state and a lookup table with a Reducer name and function, and automatically generate the Action Creator function, the action type string, and a Reducer function.

Create a cartslic. js file in the SRC /store directory and create a Redux slice Reducer using the createSlice method. Each slice contains reducer and Actions, which can achieve modular encapsulation. All related operations are done in a separate file. The code is as follows:

import { createSlice } from "@reduxjs/toolkit";

export const cartSlice = createSlice({
  name: "CART_ACTION".// Namespace, which is set to the action prefix by default when calling the action
  initialState: {
    // state Specifies the initial value of the data
    totalCost: 0.// Total amount
    totalCartNum: 0.// Count the number of carts
    cartList: [].// Shopping cart list data
  },
  reducers: {
    // The properties are automatically exported as actions and can be triggered directly by dispatch in the component
    totalCartData: (state, { payload }) = > {
      state.cartList = payload;
      state.totalCartNum = payload.reduce((a, b) = > a + b.amount, 0); // Total amount
      state.totalCost = payload.reduce((a, b) = > a + b.amount * b.unitPrice, 0);  // Count the number of carts,}}});/ / export actions
export const { totalCartData } = cartSlice.actions;

// Export reducer, which is used when creating a store
export default cartSlice.reducer;
Copy the code

We need to import the Reducer function from the empty store created above and add it to our store, telling the store to use this Slice Reducer function to handle all updates to the state by defining a field in the Reducer parameter. The code is as follows:

import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "./cartSlice";

export const store = configureStore({
  reducer: {
    cart: cartReducer,
  },
});
Copy the code

Now we can use the React-Redux hook to have the React component interact with the Redux store. We can use useSelector to read data from storage and use the useDispatch method to dispatch operations. Header Header component code is as follows:

import React from "react";
import { NavLink } from "react-router-dom";
import { Button, Badge } from "antd";
import { ShoppingCartOutlined } from "@ant-design/icons";
import { useSelector } from "react-redux";

function Header() {
  const { totalCartNum } = useSelector((state) = > state.cart);

  return (
    <div className="header">
      <NavLink to="/">
        <Button className="title" type="link">My bookstore</Button>
      </NavLink>

      <NavLink to="/cart">
        <Badge count={totalCartNum}>
          <Button
            type="primary"
            style={{ background: "#1890ff", borderColor: "#1890ff"}}shape="round"
            icon={<ShoppingCartOutlined />} / ></Badge>
      </NavLink>
    </div>
  );
}

export default Header;
Copy the code

Custom Hooks

Add and subtract items in the shopping cart using custom Hooks. Reference useSelector and useDispatch to read and write data in the react-Redux hook API in the document.js file. Code implementation please directly view the end of the github source download address.

The following three apis are not used in this DEMO, so you can check out the official examples for yourself.

createAction

Takes a string of type Action and returns an Action creation function that uses that type.

createReducer

Accept the initial state values and lookup tables of Action types into the Reducer function, and create a Reducer that handles all Action types.

createAsyncThunk

The createAsyncThunk () method can create an asynchronous action. This action will be executed with three states, such as Pending (fulfilling) and Rejected (failing). You can listen for state changes to perform different operations. ExtraReducers are used in the official example to create additional actions that listen for the status of the data fetch.

Redux-toolkit is a better solution for redux use at present, through some built-in plug-ins and code packaging to make the use of Redux more convenient, and more organized.

Mobx

Github address: github.com/mobxjs/mobx

The official website: zh.mobx.js.org/README.html

What is Mobx

MobX is a battle-tested library that makes state management simple and extensible through transparent functional responsive programming. The philosophy behind MobX is simple: anything derived from application state should be automatically acquired. These include UI, data serialization, server communication, and so on.

The core concept: MobX implements a simple, efficient and extensible state management library through responsive programming.

  • Write simple, template-free, minimalist code that accurately describes your intentions
  • Easily achieve optimal rendering, relying on automatic tracking for minimal rendering optimization
  • Architecture is free, portable, and testable

Commonly used API

  • MakeAutoObservable automatic conversion

The directory structure

Code implementation

Install Mobx

Initialize the project
npx create-react-app mobx-demo

Install the Mobx core toolkit
# mobx-react- Lite is a mobx/React package. Note that it can only be used with functional components
npm install mobx mobx-react-lite
Copy the code

makeAutoObservable

Observable, Action, and computed modifiers can be implemented directly by using makeAutoObservable in the constructor without modifiers such as Observable and Action, which makes the code simpler.

// target: Sets the properties and methods in the target object to Observable state and Action
// overrides: sets some properties or methods in the target object to normal properties
// options: Configures the object, autoBind, so that the action method always has the correct this referencemakeAutoObservable(target, overrides? , options?)Copy the code

In the SRC directory, create a store folder, which is used to manage all store submodules, and then create a class component, cartStore.js, the code implementation is as follows:

import { makeAutoObservable } from "mobx";

class Cart {
  1. Define data
  totalCost = 0; // Total amount
  totalCartNum = 0; // Count the number of carts
  cartList = []; // Shopping cart list data

  // 2
  constructor() {
    makeAutoObservable(this);
  }

  // 3. Define the action function
  totalCartData = (items) = > {
    this.cartList = items;
    this.totalCartNum = this.cartList.reduce((a, b) = > a + b.amount, 0); // Count the number of carts
    this.totalCost = this.cartList.reduce(
      (a, b) = > a + b.amount * b.unitPrice,
      0
    ); // Total amount
  };
}

// 4. Instantiate and export store
const cartStore = new Cart();
export default cartStore;
Copy the code

After the cartStore class components are created, in the store folder, create an index.js as the export entry file of all sub-modules, the code is implemented as follows:

import cartStore from "./modules/cartStore";

// Provides a way to pass data through the component tree to avoid manually passing props properties at each level.
export const stores = React.createContext({
  cart: cartStore,
});

// Create a useStores Hook to simplify the call
export const useStores = () = > React.useContext(stores);
Copy the code

Custom Hooks

Add, remove, and delete items in the shopping cart using custom Hooks. Reference useStores in the links.js file to read the data. Code implementation please directly view the end of the github source download address.

Next in the Header component, use useStores as follows:

// Observer: Monitors the observable state used by the current component, tracked by MobX, and notifies React to update the view when the state changes
import { observer } from "mobx-react-lite";
import { useStores } from "@/store";

function Header() {
  const store = useStores();

  return (
    <div className="header">.<NavLink to="/cart">
        <Badge count={store.cart.totalCartNum}>
          <Button
            type="primary"
            style={{ background: "#1890ff", borderColor: "#1890ff"}}shape="round"
            icon={<ShoppingCartOutlined />} / ></Badge>
      </NavLink>
    </div>
  );
}

export default observer(Header);
Copy the code

Component wrapped in observer, useStores reference Store, perfect.

This DEMO uses the latest version of Mobx V6, currently only using an API, friendly development experience, learning and understanding cost is medium. If you are interested, you can try to use it in small and medium-sized projects.

reason

  • TS support, using functional programming
  • Supports modularization and unified status management
  • Learning costs are low and state management libraries are popular
  • Take a look at the comparisons below to decide whether to stick with a familiar library or try a new one

Fyi: I chose the React-Redux + Redux-Toolkit combination provided by Redux for unified state management.

Comprehensive comparison of mainstream libraries

State management library Learning costs The coding cost Project type Design patterns The React Hooks to support TS friendly SSR Resolution of the code Concurrent mode compatibility Debugging can be Ecological prosperity
Redux high high universal The centralized react-redux + redux-toolkit general support Does not support support good high
Mobx In the In the Small and medium-sized decentralized mobx v6 + mobx-react-lite good support support The unknown poor In the
Recoil low low Small and medium-sized decentralized Natural support good Practice is less support support good low

conclusion

My writing is limited, time is short, and technical talent is not enough. I would like to share my recent learning achievements with you in my spare time. I am very optimistic about recoil’s status library and hope it will become the dominant one in the future. Come on FB brothers!!

If there are any errors in this article, feel free to correct them in the comments section.

Writing is not easy, if this article has a lost to help you, but also hope to point to praise to show encouragement.

Github source link: github.com/jackchen012…

Pay attention to my public number [lazy code farmers], get more project actual combat experience and a variety of source resources. If you also love technology and are fascinated by it, please add us to wechat [LazyCode520], and we will invite you to join our front-end combat communication group to learn and make progress together ~~~