Redux is used to manage the state of JavaScript applications. Official document address: Redux. This article explains how to use Redux in React, with emphasis on Redux. If need to understand the React, you can reference the React website or React introduction (introduction to a React when writing the word |, omega)).

Story synopsis

  1. What’s a Redux?

    Redux is a predictable state container for JavaScript apps.

    Redux is a predictable state container (where state refers to the object that holds information) used in JavaScript applications. Redux is used to manage state in a unified manner. The state of the entire application is stored in the Store as an object tree. The state is read-only and the only way to change the state is through Dispatch Actions, so the state change is predictable. Change the state in the store by dispatch, listen for state changes by subscribe, and get the current state by getState, so that data processing is concentrated in the Redux.

  2. Why use Redux?

    Imagine such a scene, the component 1, 2, 3 components, the component 5 and 7 components (icon under the green component) you need to use some data, because in the React, data is a one-way flow, so to change data in the component (7, 7 4 change notification component data requires components, component 4 1 change notification component data again. After changing the data in Component 1, the data is passed down the layers. And that’s just a simple application like the one shown below. If the application is more complex, it’s a mess. Therefore, we need to use Redux to conduct unified management of data (state). When a data needs to be changed, the data in the store will be changed directly, and when the data needs to be obtained, the data will be directly taken from the store.

    Figure -1 Processing process when Redux is not used

    Figure -2 Redux processing process

  3. What does React have to do with Redux?

    When you hear the name Redux, it starts with Re, just like React, so it might be misunderstood that Redux is a tool specifically designed to solve problems with React, but in fact React and Redux are separate tools. You can use Redux with React, you can use Redux with jQuery, or even Redux with JavaScript.

Redux

There are several Redux concepts you must know: Action, Reducer, and Store. The state mentioned below refers to the object that holds the information, which is stored in the Redux store.

actions

Actions is the carrier of data from the application to the Store. When we need to change data in the store, we dispatch actions. An action must have a type attribute that indicates the type of the action to be executed. Type is usually defined as a character constant. The other properties in the Action object are the information carried.

Such as:

{
    type: ADD_NUMBER,
    number: 1
}
Copy the code

Action Creator is the function that creates the action and simply returns the action. It sets the value of the action’s properties based on the parameters passed in.

Such as:

const ADD_NUMBER = 'ADD_NUMBER'
export const addNumber = number= > ({
  type: ADD_NUMBER,
  number
})
Copy the code

reducers

Reducer is a pure function that specifies how the state changes when an action dispatches to a store. Its first argument is the previous state, its second argument is the action object, and the value returned is the next state.

Pure functions:

  1. Passing the same argument returns the same result.
  2. Executing pure functions does not cause side effects. (A side effect is when a function changes its state value outside of scope, such as when a variable is defined outside the function and a variable is changed inside the function.)

You cannot do the following in the Reducer: Change its parameters. Cause side effects; Call an impure function (such as math.random ()).

The following caculate function is a simple reducer. If the createStore function does not pass in the second argument (the initial value of state), then the first call to caculate will have the value of undefined. So I set the state parameter to a default value of 0.

function caculate (state = 0, action) {
  const { number } = action
  switch (action.type) {
    case 'ADD_NUMBER':
      return state + number
    case 'SUBTRACT_NUMBER':
      return state - number
    case 'MULTIPLY_NUMBER':
      return state * number
    default: 
      return state
  }
}
Copy the code

When the application is large, it is common to use multiple small reducers, each reducer dealing with specific content. Then use the combineReducers provided by Redux to merge reducers into a reducer, and use the merged reducer as the parameters of the createStore to create the Store instance. A simple example is given to illustrate this part of the story after the store is covered below.

store

Store combines actions and reducers. The functions of Store are:

  1. Save the state of the application.
  2. throughgetState()To the state.
  3. throughdispatch(action)Update the state.
  4. throughsubscribe(listener)Register listeners. The listener is a function that executes the listener when an action is sent.
  5. performsubscribe(listener)Returns a function that can be called to unregister the listener.

There can only be one store in an app. To split data, you need to use multiple reducers.

In the following code, caculate is the Reducer created above, and addNumber is the Action Creator defined above.

import { createStore } from 'redux'

const store = createStore(caculate)
const listener = () = > console.log(store.getState()) // Print the current status
const unsubscribe = store.subscribe(listener)

store.dispatch(addNumber(1))
store.dispatch(addNumber(2))
store.dispatch(addNumber(3))

unsubscribe()
Copy the code

After store.dispatch() sends the action to the store and the reducer state is changed, the listener function will execute and print the following results:

1
3
6
Copy the code

I need another Reducer to handle label switching, and here are the corresponding Action creatore and Reducer.

const SELECT_TAB = 'SELECT_TAB'
const selectTab = tab= > ({
  type: SELECT_TAB,
  tab
}) 
function switchTab (state = 'CACULATE', action) {
  switch (action.type) {
    case 'SELECT_TAB':
      return action.tab
    default:
      return state
  }
}
Copy the code

Now you have two Reducers. Use combineReducers to combineReducers into a root reducer, set the root reducer as the first parameter of the createStore, and the initial state value as the second parameter of the createStore to create the store.

let state = {
  caculate: 0.switchTab: 'CACULATE'
}
const reducer = combineReducers({ caculate, switchTab })
const store = createStore(reducer, state)
const listener = () = > console.log(store.getState())
const unsubscribe = store.subscribe(listener)

store.dispatch(addNumber(1))
store.dispatch(addNumber(2))
store.dispatch(addNumber(3))

unsubscribe()
Copy the code

What combineReducers does is generate a function that, when called, executes each reducer and recombines the results of the execution into an object as the new state. When store.dispatch(addNumber(1)) is called, the two Reducers (caculate and switchTab) are called, and the results of their execution form a new state tree.

In the code snippet:

const reducer = combineReducers({ caculate, switchTab })
Copy the code

This is the same as the following code snippet,

function reducer (state = {}, action) {
  return {
    caculate: caculate(state.caculate, action),
    switchTab: switchTab(state.switchTab, action)
  }
}
Copy the code

It’s just that combineReducers will have more processing operations.

Use Redux in the React project

The React-Redux plugin is designed to use redux in react.

React Redux separates presentational components from Container Components.

Redux’s website has a table comparing the two:

Presentational Components Container Components
Purpose How things look (markup, styles) How things work (data fetching, state updates)
Aware of Redux No Yes
To read data Read data from props Subscribe to Redux state
To change data Invoke callbacks from props Dispatch Redux actions
Are written By hand Usually generated by React Redux

The container component can link the presentation component to the Redux. Container components are typically generated using the Connect method provided by React Redux.

React Redux

  1. The connect method

    Calling the connect method returns a wrapper function that receives the component and returns a new wrapper component. The wrapper component here is the container component mentioned above, which connects the presentation component to the Redux.

    Connect can be used by:

    connect(mapStateToProps? mapDispatchToProps? , mergeProps? , options?) (components)Copy the code
    • MapStateToProps. This parameter is a function that will be called whenever the store updates. If you don’t want to subscribe to the store update, pass null or undefined at this location. This parameter maps the Redux store state to the React component properties. The mapStateToProps format is :(state, ownProps?) => Object, state refers to the state in the Redux store, and the second parameter is the component’s own property. The object returned by this function is merged with the properties of the component.
    • MapDispatchToProps, this can be an object or a function. This parameter maps the Redux Store’s Dispatch property to the React component property. If this parameter is not passed, the component will receive the Dispatch property by default. The value of the Dispatch property is the Redux Store’s dispatch method. If the argument passed is a function, then the function looks like this :(dispatch, ownProps?) => Object, whose first argument is The Dispatch in Redux, and whose second argument is the component’s own property. This function returns an object whose each property value is a function that is merged into the component’s properties.
  2. The Provider component

    All container components need to be able to access the Redux store. If you do not use a Provider component, you can only pass the Store attribute to all container components. If there are container components deeply nested in the component tree, you may also need to pass the Store attribute to any display component. However, by wrapping the root component of the application with a Provider component, all container components in the application can access the Redux store.

For example, to define a container component, write:

import { connect } from 'react-redux'
import { addNumber, subtractNumber, multiplyNumber } from '.. /actions'
import Caculation from '.. /components/Caculation'

const mapStateToProps = state= > ({
  caculate: state.caculate
})

const mapDispatchToProps = dispatch= > ({
  plus: number= > dispatch(addNumber(number)),
  subtract: number= > dispatch(subtractNumber(number)),
  multiply: number= > dispatch(multiplyNumber(number))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Caculation)
Copy the code

Then there will be caculate, plus, subtract and multiply in the attribute of display component Caculation, where caculate is state.caculate; Plus, subtract, and multiply are three functions that can be dispatched when called.

Asynchronous access to information

Redux only supports synchronous data flows. If you want to use asynchronous actions, you need to enhance the createStore using applyMiddleware. The Redux-Thunk middleware enables synchronous or asynchronous actions to be initiated and can be designed to dispatch actions when certain conditions are met. With Redux Thunk, Action Creator can return a function whose first argument is Dispatch and second argument is getState. Start by downloading Redux-Thunk and using applyMiddleware to enhance createStore:

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'.const store = createStore(rootReducer, applyMiddleware(thunk))
Copy the code

With the help of The Thunk middleware, you can use asynchronous actions as follows. An action is fired at both the start and end of a request. An action fired at the start of a request can be used to show a loading diagram, etc. The action is simply triggered at the start of the request, and no further use is made.

const requestLeaderBoard = () = > ({
  type: REQUEST_LEADERBOARD
})

const receiveLeaderBoard = (json) = > ({
  type: RECEIVE_LEADERBOARD,
  leaderboard: json.data
})

export function fetchLeaderBoard () {
  return dispatch= > {
    dispatch(requestLeaderBoard())
    return fetch('Here uses the address of a GET request')
      .then(res= > res.json())
      .then(json= > dispatch(receiveLeaderBoard(json)))
  }
}
Copy the code

To learn redux, I wrote an exercise: source code addresses

Problems encountered

  1. The number of times the Reducer is executed as “redundant”.

    • Scenario 1: If combineReducers is not used, reducer will be executed one more time. The action type is:@@redux/INITd.c.p.m.e.7.
    • Case 2: After combineReducers is used, each Reducer is executed three more times. The action types are as follows:@@redux/INITf.1.c.7.u.x,@@redux/PROBE_UNKNOWN_ACTIONz.n.g.2.k.9,@@redux/INITf.1.c.7.u.x. There are three redundant reducer calls.

    The problem of case 2 consulted my lovely colleague Boss Yang, boss Yang found the corresponding source code to give the answer, and then I found in the source code in the same way, got the answer of case 1.

    (1) The reasons for the occurrence of situation 1 are as follows:

    When a store is created using the createStore, Redux dispatches an initial action (as shown below) to ensure that each Reducer returns its default state.

    dispatch({ type: ActionTypes.INIT })
    Copy the code

    (2) The reasons for the occurrence of situation 2 are as follows:

    The first action@@redux/ initF.1.C.7.u.x is executed because redux calls reducer once when executing combineReducers. This is to check if the initial state returned by the call to reducer is undefined. If it’s undefined it throws an error.

    const initialState = reducer(undefined, { type: ActionTypes.INIT })
    Copy the code

    The second action@@redux/ probe_unknown_actionz.n.G. 2.K.9 is also executed during the combineReducers execution. The purpose is to check whether the action name and the private name in the redux are the same. If so, It throws an error.

    The third action is the same as case 1.