An overview of the

Redux is a state management tool. For example, each component can have its own state or state passed in from the props or context. When the state in the React component changes, the component tree with the current component as the root node is rerendered. Of course, there are ways (such as react.Memo) to avoid unnecessary rendering.

React maintains state and UI synchronization from all three sources. How does redux work? In addition to Redux, redux-Thunk, Redux-Saga, and Redux Tookit will be discussed on this basis.

The story itself

Redux is itself a state container, and data is stored in stores as a tree. Just as React always stresses immutable, data in Redux is also immutable.

The only method that can change the state is the Dispatch action. The Dispatch is the only method that can change the state. The action is a common object that contains type and payload attributes and is used to describe the operation on the state.

Actions are sent to the Reducer, which is a pure function that manipulates state and returns a new state based on the action’s instructions. A store can have a reducer to take full responsibility, or it can be modularized and divided into several reducer processes. Note that although modularization is used here, actions are received without differentiating modules and will be processed as long as the corresponding type is matched.

When state changes, callbacks to this data source are executed, i.e., publish and subscribe mode, which is the handwritten eventEmitter that is often examined in interviews.

Redux provides only a few apis, and while there are some recommended best practices, there’s a lot of freedom to use, just like React itself.

Import {createStore} from 'redux' // Modify state reducer, including default state function counterReducer(state = {value: 0 }, action) { switch (action.type) { case 'counter/incremented': return { value: state.value + 1 } case 'counter/decremented': return { value: state.value - 1 } default: Return state}} let store = createStore(counterReducer) // Register store.subscribe(() => Console.log (store.getState())) // Change, each change triggering a previously registered callback store.dispatch({type: 'counter/ increstate '}) // {value: 1} store.dispatch({ type: 'counter/incremented' }) // {value: 2} store.dispatch({ type: 'counter/decremented' }) // {value: 1}Copy the code

Redux asynchronous middleware

The state in the store is changed using Dispatch, which is a simple function call that is received by the pure reducer function without any time for asynchrony (and execution side effects).

In fact, it is very easy to implement asynchrony, just finish the asynchrony and side effects before dispatch, which is also the work process of asynchronous middleware. The middleware in question, mainly Redux-Chunk and Redux-Saga, encapsulates the above process.

The middleware

Where is the middleware in between?

When we dispatch an action, we will not directly reach the reducer, but pass a series of middleware. The middleware can access the store and the current action, and then pass the action to the next middleware or reducer after any operation to complete the instructions expressed by the current action.

redux-chunk

Redux-chunk calls the current function if the action is a function, passing in dispath and getState as arguments. The function can dispatch the actual action after executing any operation.

function createThunkMiddleware(extraArgument) {
  return function (_ref) {
    var dispatch = _ref.dispatch,
        getState = _ref.getState;
    return function (next) {
      return function (action) {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }

        return next(action);
      };
    };
  };
}

var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
Copy the code

Chunk means, as indicated here, to postpone the execution of a job, that is, to postpone dispatch until after the asynchronous execution

redux-saga

Redux-saga sees itself as an upgrade to Redux-Chunk (essentially a delayed execution of Dispatch), avoiding callbacks with generator functions and making it easier to test. In addition to these benefits, many new concepts and apis were introduced and react became less and less popular.

Saga is a very technical term and can be thought of as a worker thread responsible for executing a side effect.

Each saga is a generator function that can access (select) and modify (put) the redux state. Yield is followed by an effect, or side effect, which can be a promise, but is not recommended because it is not easy to test. The recommended approach is to invoke another saga or normal function, such as call or fork, to perform a synchronous and asynchronous task. These tasks can be organized in other ways than sequential execution, such as promise.all.

On the whole, saga can be divided into two types: Watcher and worker, that is, the former type is used to monitor the corresponding type of action, and then call the latter type of real execution side effect, and then dispatch another action. To indicate asynchronous loading, you may need to define and dispatch other actions, which can be tedious to write.

To sum up, Redux-Saga divided a side-effect function with high degree of freedom in Redux-chunk into two stages, and each stage can be organized in different forms. Finally, put was used to send actions to reducer to complete the whole process

react-redux

Redux is used in react. In the React application, you can use useState for states within a single component. State shared by multiple components can be promoted to a common parent component and passed in as props; More complex state sharing, such as user information, can be stored in the root component using context, instead of passing in layers as props. So the question is, what does redux do? It’s very similar to the third state, it’s used specifically to store states, but you don’t have to use it.

React – Redux is a layer of encapsulation that connects React and Redux. It converts a common data container into a React application state. The use of state can be divided into read and write parts.

Basic usage

Add a component outside the app root component and pass in the store

import React from 'react' import ReactDOM from 'react-dom' import './index.css' import App from './App' import store from './app/store' import { Provider } from 'react-redux' ReactDOM.render( <Provider store={store}> <App /> </Provider>,  document.getElementById('root') )Copy the code

// Use corresponding hooks to access stores and dispatch actions in specific components.

import React, { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'
import styles from './Counter.module.css'

export function Counter() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}
Copy the code

React Redux Quick Start for a complete example

read

In the component Provider source code, store is passed in as the value of the context, so all of its children can access it.

write

So far, our React app has access to and changes to the Store, but we should also know that redux is now just like a global variable and does not cause the page rerender to change like React State.

The concrete implementation here is that when the store state changes, it will compare the state before and after, and execute forceRender to force rerendering.

const [, forceRender] = useReducer((s) => s + 1, 0)
Copy the code

redux-toolkit

Because the redux core only contains a small number of functions, a large amount of middleware needs to be introduced to deal with complex functions, and a large amount of Boilerplate/Verbosity code needs to be written for best practices. Because Redux officially launched Toolkit on this basis to simplify the use of Redux in complex scenarios.

Note that the essence of The Redux-Toolkit is a simplification of existing functions, there is not much to understand, but familiar, here is a general introduction.

  • ConfigureStore encapsulates createStore and comes with Redux-Thunk and DevTool
  • CreateSlice further encapsulates createReducer() and createAction. Create actions when reducer is created and dispatch directly after export. Immer is built in. The Switch case was omitted when the Reducer was created.
  • createAsyncThunkA chunk is first created and then added to the Reducer, and the final dispatch will be automatically dispatched according to the state of the promisepending.fulfilled, and rejectedThree types of action.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'

// First, create the thunk
const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)

// Then, handle actions in your reducers:
const usersSlice = createSlice({
  name: 'users',
  initialState: { entities: [], loading: 'idle' },
  reducers: {
    // standard reducer logic, with auto-generated action types per reducer
  },
  extraReducers: (builder) => {
    // Add reducers for additional action types here, and handle loading state as needed
    builder.addCase(fetchUserById.fulfilled, (state, action) => {
      // Add user to the state array
      state.entities.push(action.payload)
    })
  },
})

// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
Copy the code
  • The createEntityAdapter is used to manipulate data stored in a store as if it were a database, what and Why

RTK query

RTK Query is also part of the RTK Toolkit to handle network requests and cache-related functions. When we used createAsyncThunk to process requests, we also needed to manually maintain the reducer in various states, which is further encapsulated here.

There is not much to say here, see the documentation for specific usage, or refer to this article.