Use React official Hooks instead of redux complete solution

redux as we all know, it is the react Management Library of the third party. It is difficult for many people to make complaints about it and has to use it. I recently found that most of the functions of redux can be implemented only with react official hooks! It is easier to use than redux, and the amount of code is less.
I wrote a complete demo here. Generally, small and medium-sized projects can refer to it, and only official Hooks can be used to replace redux. Here are the complete steps and codes:

New project

Create a react project using the official template:

create-react-app demo

Clean up public / directory

Delete all files in the directory, and then create index.html in the directory, as follows:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Demo</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

Clean up src / directory

Delete all files in the directory, and then create index.js and app.js in the directory, as follows:

index.js

import ReactDOM from 'react-dom';
import App from './App';

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

app.js

import Demo from "./components";
import Context from "./context";

export default function App() {
  return (
    // Use the context component to wrap the sub components that need to share data
      <Context>
        <Demo />
      </Context>
  );
}

context

Create the context directory under src /. It is a container for the entire project to share and manipulate data.

context entry

First, create the index.js file in the src/context directory, which is the entry of the whole context. Here, two react hook s, createContext and useReducer, are used. What you do here is equivalent to defining the store of redux.

index.js

import { createContext, useReducer } from "react";
// Multiple sets of reducer s can be introduced into one context
import Color from './Font/reducer'
import Food from './Food/reducer'

// Create context
export const MyContext = createContext();

/**
 * Create a Context component, which can contain multiple sets of reducers, and each set of reducers has corresponding data and actions
 * If the subcomponent needs color related data in the context, it can access the data by calling colorContext and update the data by calling colorDispatch
 * Similarly, when the subcomponent needs food related data in the context, it can access the data by calling foodContext and update the data by calling foodDispatch
 */
export default function Context(props) {
    const [colorContext, colorDispatch] = useReducer(Color, { color: "blue", font: 20, size: 1 })  // color data and action initialization
    const [foodContext, foodDispatch] = useReducer(Food, { count: 10 },)  // food data and action initialization
    return (
        <MyContext.Provider value={{ colorContext, colorDispatch, foodContext, foodDispatch }}>
            {props.children}
        </MyContext.Provider>
    );
};

Create data and action directories

In this demo, many data and actions need to be shared. Here, two subdirectories are created according to the data function.

Create Font directory

The directory is located in src/context/Font, which stores data and actions related to font adjustment. There are three files in this directory, which store constants, actions and processors respectively.

constant.js

export const UPDATE_COLOR = "updateColor"
export const UP_FONT = "upFont"
export const DOWN_FONT = "downFont"

action.js

// Generate action objects specifically for reducer
import { UPDATE_COLOR, UP_FONT, DOWN_FONT } from './constant'

// Synchronization action
export const updateColor = data => ({ type: UPDATE_COLOR, data })
export const upFont = data => ({ type: UP_FONT, data })
export const downFont = data => ({ type: DOWN_FONT, data })

// Asynchronous action
export const upFontAsync = (dispatch, data, time) => {
    setTimeout(() => {
        dispatch(data)
    }, time)
}

reducer.js

import { UPDATE_COLOR ,UP_FONT,DOWN_FONT} from './constant'

// Define reducer
export default function reducer(state, action) {
    switch (action.type) {
        case UPDATE_COLOR:
            return { ...state, color: action.data.color }  // When multiple values are stored in state, first expand state, then update the color key value pair, and then save back to state
        case UP_FONT:
            return { ...state, font: state["font"] + action.data.size }
        case DOWN_FONT:
            return { ...state, font: state["font"] - action.data.size }
        default:
            return state
    }
}

Create Food Directory

The directory is located in src/context/Food, which stores data and actions related to adjusting food. There are three files in this directory, which store constants, actions and processors respectively.

constant.js

export const INCREASE = "Increase"
export const DECREASE = "Decrease"

action.js

// Generate action objects specifically for reducer
import { INCREASE ,DECREASE} from './constant'

// Synchronization action
export const increase = data => ({ type: INCREASE, data })
export const decrease = data => ({ type: DECREASE, data })

reducer.js

import { INCREASE ,DECREASE} from './constant'

// Define reducer
export default function reducer(state, action) {
    switch (action.type) {
        case INCREASE:
            return { count: state["count"]+1 }
        case DECREASE:
            return state["count"]>1?{ count: state["count"]-1 }:{ count: 0 }
        default:
            return state
    }
}

components

Create the components directory in the src / directory, where all the jsx files are stored. Because this is a demo, page, container and router will not be divided.

List of documents

index.jsx

import Font from "./Font";
import ShowArea from "./ShowArea";
import Food from "./Food";

export default function Demo() {
    return (
        <>
            <ShowArea />
            <Font />
            <Food />
        </>
    );
}

ShowArea.jsx

import React, { useContext } from "react";
import { MyContext } from "../context";

const ShowArea = (props) => {
    console.log("Here is ShowArea")
    const { colorContext } = useContext(MyContext);  //  Get data from context
    const { foodContext } = useContext(MyContext);  //  Get data from context
    // Get the specified value from the context by using ["key"]
    return (
        <>
            <div style={{ color: colorContext["color"], fontSize: colorContext["font"] }}>The font color is displayed as{colorContext["color"]}</div>
            <div>Meat bags are still available{foodContext["count"]}One!</div>
        </>
    );
}

export default ShowArea

Font.jsx

import React, { useContext, useRef, useMemo } from "react";
import { MyContext } from "../context";
import { updateColor, upFont, downFont, upFontAsync } from '../context/Font/action.js'

const Inner = (props) => {
    console.log("Here is ChangeFont Render")
    const { color, dispatch } = props;
    let size = useRef();
    return (
        <>
            <br />
            <button
                onClick={() => {
                    dispatch(updateColor({ color: "red" }));
                }}
            >
                gules
            </button>
            &nbsp;
            <button
                onClick={() => {
                    dispatch(updateColor({ color: "blue" }));
                }}
            >
                blue
            </button>
            <br />
            <br />
            <select ref={size}>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
            &nbsp;
            <button
                onClick={() => {
                    dispatch(upFont({ size: size.current.value * 1 }));
                }}
            >
                The font becomes larger
            </button>
            &nbsp;
            <button
                onClick={() => {
                    dispatch(downFont({ size: size.current.value * 1 }));
                }}
            >
                Font becomes smaller
            </button>
            &nbsp;
            <button
                onClick={() => {
                    upFontAsync(dispatch, upFont({ size: size.current.value * 1 }), 1000)
                }}
            >
                Asynchronous font size
            </button>
            <br />
            <div style={{ color: color["color"], fontSize: color["font"] }}>The font color is displayed as{color["color"]}</div>
        </>
    )
}

const Font = (props) => {
    const { colorContext, colorDispatch } = useContext(MyContext);
    return useMemo(
        () => {
            return (
                <>
                    <Inner color={colorContext} dispatch={colorDispatch} />
                </>)
        }
        , [colorContext, colorDispatch])
}

export default Font;

Food.jsx

import React, { useContext, useMemo } from "react";
import { MyContext } from "../context";
import { increase, decrease } from '../context/Food/action'

const Inner = (props) => {
    console.log("Here is food Render")
    const { food, dispatch } = props
    return (
        <>
            <br />
            <br />
            <button
                onClick={() => {
                    dispatch(increase({ color: "red" }));
                }}
            >
                Add meat bag
            </button>
            &nbsp;
            <button
                onClick={() => {
                    dispatch(decrease({ color: "blue" }));
                }}
            >
                Reduce meat bags
            </button>
            <br />
            <div>Meat bags are still available{food["count"]}One!</div>
        </>
    )
}

const Food = (props) => {
    const { foodContext, foodDispatch } = useContext(MyContext);
    return useMemo(
        () => {
            return (
                <>
                    <Inner food={foodContext} dispatch={foodDispatch} />
                </>)
        }
        , [foodContext, foodDispatch])
}

export default Food;

Comparison between context and redux

store comparison

context

import { createContext, useReducer } from "react";
import Color from './Font/reducer'
import Food from './Food/reducer'

export const MyContext = createContext();

export default function Context(props) {
    const [colorContext, colorDispatch] = useReducer(Color, { color: "blue", font: 20, size: 1 })  // color data and action initialization
    const [foodContext, foodDispatch] = useReducer(Food, { count: 10 },)  // food data and action initialization
    return (
        <MyContext.Provider value={{ colorContext, colorDispatch, foodContext, foodDispatch }}>
            {props.children}
        </MyContext.Provider>
    );
};

redux

import {createStore,applyMiddleware} from 'redux'
import reducer from './reducers'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))

Summary

advantage

  1. Multiple data root nodes can be defined in context, and redux has only one store root node.
  2. context does not need to download and import redux and redux truck packages additionally, and the file size after packaging can be slightly reduced.

inferiority

There is no exclusive debugging tool for using context. The redux exclusive debugging tool redux devtools extension is very powerful!

reducer comparison

Comparison between context and redux

Summary

advantage

  1. context can be divided into directories according to the purpose of data. All reducer related files with the same purpose are placed in one folder. Different data are on different nodes, which is easy to maintain.
  2. context does not need to import combineReducers to summarize multiple reducers.

inferiority

Not yet!
In addition, when using context for data initialization, I did it in the store; Using redux is usually done in reducer. This is a matter of coding style. It doesn't matter whether it's good or bad.

Comparison of calling methods

context

  • Please note that the three red boxes surround the subcomponents with custom context components. In these subcomponents, you can get the data and actions in the context.
  • Look at the green box. First, use the createContext of react in the index.js file defining the context to define the context component name, and then use the useContext of react in the sub component to obtain the specified data set and the corresponding action set!

redux


redux is widely used and familiar. The way redux uses data is to use connct to connect the data and methods in the store to the state.

Summary

This part is not good or bad, but the difference of writing methods. If you have to compare the advantages and disadvantages, context has a slight advantage. The use method is in line with useState, which is in line with everyone's habits; The use of redux is a bit strange. I can't think of such a use without looking at the documents.

Performance optimization of context

Many people have written the previous part. Maybe few people like me directly compare the two one by one. There are few reliable articles on conten t performance optimization, detailing a convenient and easy-to-use method. Here I'll talk in detail about the performance optimization when using context.
There are already optimized codes above. Here's another picture. To see the text code, please look forward.

The idea of performance optimization is to split the component into two parts. The container component is responsible for introducing context data and actions, and the UI component is responsible for rendering. Use useMemo to monitor whether the context data and actions have changed. If there is a change, the UI component will be called to re render the component. If there is no change, the UI component will not be called to re render the component.
It should be noted that whether useMemo monitoring data changes also has overhead. If the current component content is not much, it is not necessary to use useMemo monitoring!
Look at the following figure:

Here, the upper div displays the information related to the font component, and the lower div displays the information related to the food component. If useMemo is used to monitor data, only the div above can be refreshed when the relevant data of font component changes, and only the div below can be refreshed when the relevant data of food component changes, but in fact, it will reduce the performance and increase a lot of code.
In short, please master the optimization method and the optimization scale. If multiple sub components are nested in a component and the re rendering cost is large, useMemo should be used for optimization. On the contrary, if the re rendering cost of a component is very small, there is no need to optimize it with useMemo!!!

last

After careful comparison and sorting, I found that only debugging tools are dominant in redux. In addition, useContext, createContext, useReducer and useMemo can be used to replace Redux!!! Using react official hooks has less code, smaller generated files, and better reliability and maintainability of official tools!!!
Do you agree with me after carefully reading the whole article? Use React official Hooks to completely replace redux!
Of course, there are some projects that are too complex to use redux. In that case, using these four hooks is also difficult to solve the problem!

Keywords: Javascript node.js React

Added by [PQ3]RogeR on Thu, 07 Oct 2021 03:00:37 +0300