preface

After leaving the job, I began to calm down and think about some problems that I had no time to think about in the heavy pace of business development. The topic of this issue is the pure function hook useReducer and the shared state hook useContext.

What is a Reducer function?

In React, the Reducer function is an important concept. It represents a function that receives the old state and returns the new state.

const nums = [1.2.3]
const value = nums.reduce((acc, cur) = > acc + cur, 0)
Copy the code

In the example above, one of the arguments to the reduce function is a standard Reducer function.

SetState (prev => prev + 1) useState (prev => prev + 1) React basicStateReducer basicStateReducer basicStateReducer

function basicStateReducer(state, action) {
  return typeof action === 'function' ? action(state) : action;
}
Copy the code

So, when our setter receives a function as an argument, the old state will be used by that function as an argument.

useReducer

The basic uses of useReducer are as follows:

const [state, dispatch] = useReducer(reducer, initialState, initFunc);
Copy the code

The third parameter is optional, and we usually only use the first two.

A simple example (implementing the number +1) is as follows:

import React, { useReducer } from "react";

const initialState = {
  num: 0};function reducer(state, action) {
  switch (action.type) {
    case "increase":
      return { num: state.num + 1 };
    case "decrease":
      return { num: state.num - 1 };
    default:
      throw new Error();
  }
}

const NumsDemo = () = > {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      <h1>Numeral: {state.num}</h1>
      <button onClick={()= > dispatch({ type: "increase" })}>+</button>
      <button onClick={()= > dispatch({ type: "decrease" })}>-</button>
    </>
  );
};

export default NumsDemo;
Copy the code

As you may have noticed, useReducer is very similar to useState: the logic for defining and modifying states. UseReducer is more difficult to use, but if the state you need to maintain is complex and multiple states depend on each other, then the benefits of useReducer really show up: using a semantic action to hide the complex details of changing the state.

useContext

UseContext is simple. It is used to share data in a local component tree. It is similar to provide/inject in VUE.

// Defined in the module entry file
// Home.tsx
export const MyContext = React.createContext({num: 24});

// In a component inside the module
// Sub.tsx
import MyContext from '.. /Home';

const Sub = () = > {
  const state = useContext(MyContext);
  // ...
}
Copy the code

The basic concept of Redux and what problems does it solve

Ok, now that we’ve decided to make a small Redux, we need to review the basic concept of Redux before doing so.

When sharing data for small pages, we typically have development conventions such as “state promotion,” which means that we put the shared state to the nearest public parent. But as the number of pages increases and the granularity of component separation becomes finer, this “shared state” mechanism becomes fragile and redundant.

The mechanics of Redux are designed to solve this problem, and Redux has several distinct features:

  1. The only source of truth for the data;
  2. Reducer pure function;
  3. Single data flow.

The single data flow of Redux can be summarized into three parts: View, Reducers and Store.

The View layer initiates the update action (Dispatch) and arrives at the Reducers function layer. The update function executes and returns the latest state and arrives at the State layer (Store). After the update of the state layer, the View layer will be notified of the update.

In my opinion, both Vuex and Redux are designed as a “front-end database”. Only public state should be stored in the Store layer. It is not recommended to store other things, such as public methods, as this goes against the reducer concept of pure functions.

Implement a simple, small Redux

Ok, to get things started, we define only three components here: the root component App, the first child Sub1, and the second child Sub2. Implement a very simple number addition and subtraction function, as follows:

// Note: The ts type definition has been removed to make the code more readable
// App.tsx
import React, { useReducer } from "react";
import Sub1 from "./Sub1";
import Sub2 from "./Sub2";

const INITIAL_STATE = {
  name: "zhang".age: 24};// Define several operations that change the state
function reducer(state, action) {
  switch (action.type) {
    case "increaseAge":
      return { ...state, age: state.age + 1 };
    case "decreaseAge":
      return { ...state, age: state.age - 1 };
    case "changeName":
      return { ...state, name: action.payload };
    default:
      returnstate; }}// Selectively export the context
export const AppContext = React.createContext(null);

function App() {
  const [state, dispatch] = useReducer(reducer, INITIAL_STATE);

  return (
    <AppContext.Provider value={{ state.dispatch}} >
      <Sub1 />
      <Sub2 />
    </AppContext.Provider>
  );
}

export default App;


/ / in Sub1. The TSX
import React, { useContext } from "react";
import { AppContext } from "./App";

const Sub1 = () = > {
  const { state, dispatch } = useContext(AppContext);
  return (
    <>
      <h1>Sub1 age: {state.age} name: {state.name}</h1>
      <button onClick={()= > dispatch({ type: "increaseAge" })}>+</button>
    </>
  );
};

export default Sub1;

/ / in Sub2. The TSX
import React, { useContext } from "react";
import { AppContext } from "./App";

const Sub2 = () = > {
  const { state, dispatch } = useContext(AppContext);
  return (
    <>
      <h1>Sub2 age: {state.age} name: {state.name}</h1>
      <button onClick={()= > dispatch({ type: "decreaseAge" })}>-</button>
    </>
  );
};

export default Sub2;
Copy the code

Running the example above, you can see that when a child component changes the public state, all other components (consumers) that consume the public state are updated. This effectively avoids the problem of code bloatiness and fragility caused by props.

Can useReducer + useContext replace redux?

No, ALTHOUGH I have used it in the project, I still find it lacking in comparison to Redux. The most typical problem is that there is no callback after updating the public status.

UseReducer + useContext actually creates a “poor man’s Version of Redux”. And we had to put extra effort into avoiding performance issues (react.Memo, useCallback, etc.), while tools like React-Redux had already taken care of those annoying dirty works for us. In addition, we will also face the following problems:

  • You need to implement auxiliary functions such as combineReducers
  • Lost middleware support for the Redux ecosystem
  • Lost debugging tools such as Redux DevTools
  • Out of the hole is not good for asking for help…

Redux with Hooks: Redux with Hooks