Redux, react Redux learning

Time:2021-12-9

preface

Buddy, who make contact with Redux, React-Redux, and unfamiliar partners in the process of learning React, may be confused with Redux. Why do we still have React-Redux? Let us first make complaints about the use of Redux and what tucks are in use. Let’s see why React-Redux appears.

1、 Redux

Before you start, remember

Redux is a famous JavaScript state management container

In other words, in addition to using Redux with react, it can also configure JS and Vue for use

1.1 design idea

  • ReduxIs to store the entire application state in a file calledstoreWhere there is a state treestate tree
  • Components can be accessed throughstore.dispatchDistribution behavioractiontostore, storeIt will not be modified directlystate, but written by the userreducerThe next life becomes a new onestate, and return tostore
  • Other components through subscriptionstoreRefresh your view by changing the state in

redux-flow.png

1.2 three principles

  • The entire application has only one store, and its internal state tree stores the state of the entire application
  • The state is read-only. You can only modify the state by sending actions. In order to describe how actions change the state, you need to write a reducer pure function
  • The design of a single data source makes the communication between react components more convenient and is also conducive to the unified management of state

1.3 createStroe

Write a counter counter to learn about Redux related APIs

1.3.1 store

// ./src/store.js
import {createStroe} from 'react'

function reudcer(){}
let store = createStore(reudcer)

adoptcreateStoreMethod can create astore, you need to pass a parameter reducer (PS: described later), and store is an object. The following methods can be called

  • store.getState(),Get the latest state tree
  • store.dispatch(), distribution action
  • store.subscribe(), subscribe to the change of state in the store

1.3.2 reducer

Reducer must be a pure function to receivestate, actionTwo parameters. State is the old state and cannot be modified directly. Instead, it needs to be modified according to action If the type is different, generate a new state and return it

// ./src/store.js
import {createStroe} from 'react'
export const ADD = 'ADD'
export const MINUS = 'MINUS'
function reducer (state = {count: 0}, action) {
  console.log('action', action); // {type: 'xxx'}  
  switch(action.type) {
    case ADD:
      return {count: state.count + 1}
    case MINUS:
      return {count: state.count - 1}
    default:
      return state
  }
}

let store = createStore(reudcer)
export default store

Note that in the code above, givestateThe initial value is set{count: 0}, next, inCounterComponent to use this exportedstore

1.3.3 getState、dispatch、subscribe

// ./src/components/Counter.jsx
import React from 'react'
import store from '../store'

class Counter extends React.Component{
  constructor(props){
    super(props)
    this.state = {
      number: store.getState().count
    }
  }
  render () {
    return <div>
      <p>{this.state.number}</p>
      <button onClick={() => store.dispatch({type: 'ADD'})}>+</button>
      <button onClick={() => store.dispatch({type: 'MINUS'})}>-</button>
    </div>
  }
}

export default Counter

stayCounterComponent, through store Getstate() to get the latest state. Click the button, and the store. Info will be displayed Dispatch sends action to the store (PS: Please note that action is an object and must have a type attribute). The store will pass the current state and action to reducer to generate a new state to update the status. Unfortunately, the number on the page has not changed

Screenshot 1.46 PM, November 14, 2020 43.png

You can see that the action has been accepted in the reducer function. At this time, the state in the store has changed, and the page is not updated because the counter does not subscribe to the change of state in the store. You can add the following code to the code

class Counter extends React.Component{
  componentDidMount () {
    this.unSubscribe = store.subscribe(() => {
      this.setState({
        number: store.getState().count
      })
    })
  }
  componentWillUnmount () {
    this.unSubscribe && this.unSubscribe()
  }
}

usestore.subscribeThis method accepts a function whenstoreinstateWhen the state changes, the incoming function will be executed, andstore.subscribeMethod returns a function for unsubscribing.

So far, the counter component has been basically implemented. Some small partners may find that the console outputs after the application is loaded for the first time

action {type: "@@redux/INIT1.s.m.m.c.n"}

This isstoreTo getstateInitial value of{count: 0}, it will be distributed automatically onceaction {type: "@@redux/INIT1.s.m.m.c.n"}

Small partners familiar with the “publish subscribe” model may see that,ReduxThe “publish subscribe” mode is used internally. Next, we try to implement a crude versionRedux

1.3. 4. Implement createtrace

function createStore(reducer){
    let state
  const listeners = []

  //Returns the latest state
  function getState () {
    return state
  }

  //Dispatch action
  function dispatch(action){
    state = reducer(state, action)
    listeners.forEach(listener => listener())
  }

  //Subscribe, return unsubscribe function
  function subscribe(listener){
    listeners.push(listener)
    return function () {
        const index = listeners.indexOf(listener)
      listeners.splice(index, 1)
    }
  }

  //Get state default
  dispatch({type: "@@redux/INIT1.s.m.m.c.n"})

  //Return store, an object
  return {
    getState,
    dispatch,
    subscribe
  }
}

export default createStore

Through the test, we have realized the function of the counter component of the rudimentary version of redux

1.4 bindActionCreators

1.4. 1 Principle and Application

In the counter component, we directly use store Dispatch dispatch action

<button onClick={() => store.dispatch({type: 'ADD'})}>+</button>
<button onClick={() => store.dispatch({type: 'MINUS'})}>-</button>

The defect of the above writing method is that the store is written repeatedly Dispatch and action Type is easy to be misspelled but not easy to find. At this time, Redux provides the bindactioncreators function, which combines the function of sending action with store Bind with dispatch

// ./src/components/Counter.jsx
import React from 'react'
import {bindActionCreators} from 'redux'
import store from '../store'

//The add function returns action, so this function can be called actioncreator
function add() {
  return {type: 'ADD'}
}

function minus() {
  return {type: 'MINUS'}
}

const bindAdd = bindActionCreators(add, store.dispatch)
const bindMinus = bindActionCreators(minus, store.dispatch)

class Counter extends React.Component{
    // ...
  render () {
    return <div>
      <p>{this.state.number}</p>
      <button onClick={bindAdd}>+</button>
      <button onClick={bindMinus}>-</button>
    </div>
  }
}

export default Counter

In fact, the bindactioncreators logic can be extracted into a separate file in the code and can be used in other components. At the same time, the defect of the above code is that each function needs to be bound manually, which is unreasonable. Therefore, bindactioncreators supports passing in objects and wrapping all actioncreator functions into objects

// ./src/components/Counter.jsx
import React from 'react'
import {bindActionCreators} from 'redux'
import store from '../store'

//The add function returns action, so this function can be called actioncreator
function add() {
  return {type: 'ADD'}
}

function minus() {
  return {type: 'MINUS'}
}

const actions = {add, minus}

const bindActions = bindActionCreators(actions, store.dispatch)

class Counter extends React.Component{
  // ...
  render () {
    return <div>
      <p>{this.state.number}</p>
      <button onClick={ bindActions.add }>+</button>
      <button onClick={ bindActions.minus }>-</button>
    </div>
  }
}

export default Counter

1.4. 2 handwriting implementation

function bindActionCreators (actionCreater, dispatch) {
  //Actioncreator can be a function / object
  if (typeof actionCreater === 'function') {
    return function (...args) {
        return dispatch(actionCreater(...args))
    }
  } else {
    let bindActionCreaters = {}
    Object.keys(actionCreater).forEach(key => {
        bindActionCreaters[key] = function (...args) {
        return dispatch(actionCreater(...args))
      }
    })
    return bindActionCreaters
  }
}

export default bindActionCreaters

1.5 combineReducers

1.5. 1 Principle and Application

When an application contains multiple modules, it is unreasonable to put the state of all modules in the same place. A better way is to divide them according to modules. Each module has its own reducer and action. Finally, it is merged into a large reducer through combinerreducers in redux

// src\store\reducers\index.js
import {combineReducers} from 'redux';
import counter1 from './counterReducer1';
import counter2 from './counterReducer2';
export default combineReducers({
    x: counter1,
    y: counter2
});

// src/store/reducers/counterReducer1.js
import * as types from '../action-types';
export default function (state= {count: 0},action){
    switch(action.type){
        case types.ADD1:
            return state.count + 1;
        case types.MINUS1:
            return state.count - 1;
        default:
            return state;
    }
}

// src/store/reducers/counterReducer2.js
import * as types from '../action-types';
export default function (state= {count: 0},action){
    switch(action.type){
        case types.ADD2:
            return state.count + 1;
        case types.MINUS2:
            return state.count - 1;
        default:
            return state;
    }
}

combineReducersMethod accepts an object. The attribute key can be set arbitrarily. The attribute value corresponds to the reducer function of each module and returns the final combined reducer method.

After merging through reducer, the state trees in the store will also be divided according to modules

store.getState() 
{
  x: {count: 0}
  y: {count: 0}
}

In this way, in the component, the use of state needs to be modified as follows

import store from '../store';
export default class Counter extends Component {
    constructor(props) {
        super(props);
        this.state = {
          value: store.getState().x.count
        }
    }
    //...
}

When distributed in componentsactionWhen,actionWill be passed tocombineReducersIn the returned function, the respective function of each module will be calledreducerGenerate their own newstateEventually, sostateAfter merging, updatestoreMediumstate

1.5. 2 handwriting implementation

function combineReducers(reducers){
  //Returns the reducer function after merging
  return function (state, action){
    const nextState = {}
    Object.keys(reducers).forEach(key => {
        nextState[key] = reducers[key](state[key], action)
    })
    return nextState
  }
}

It can be seen that the main actions are distributed, and the reducer function of each module will execute

1.6 summary

It can be seen that using stores in the react component requires manually importing store files and manually subscribing to state changes in the store, which is unreasonable. Next, let’s see how react Redux solves it

2、 React Redux

2.1 principle and Application

React Redux provides a provider component. Through the provider component, stores can be passed to its child components and grandchildren without manually importing each component

// ./src/index.js
import { Provider } from 'react-redux'
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

In the descendant component counter1, the connect function provided by react Redux can be used to associate the store with the props of the counter1 component

import React from 'react'
import { connect } from 'react-redux'
import action from '../store/actions/Counter1'

class Counter1 extends React.Component{
  render () {
    return <div>
      <p>{ this.props.count }</p>
      <button onClick={ this.props.add }>+</button>
      <button onClick={ this.props.minus }>-</button>
    </div>
  }
}

const mapStateToProps = state => state
const mapDispatchToProps = {
  ...action
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter1)

From the above code, we can see that inside the counter1 component, properties or methods are accessed through props. We can completely convert the counter1 component into a function component (stateless component), and wrap it through a container component (stateful component) outside the function component. All connect (mapstatetoprops, mapdispatchtoprops) (counter1) Finally, a container component is returned. Next, let’s see how to write a react redux

2.2 handwriting implementation

To transfer stores across components, react Redux uses the react context API internally

Create a reactreduxcontext context object

// src/react-redux/Context.js

import React from 'react'
export const ReactReduxContext = React.createContext(null)
export default ReactReduxContext

In the proveider component, you need to use the provider component provided in the reactreduxcontext object

// src/react-redux/Provider.js
import React from 'react'
import {ReactReduxContext} from './Context'

class Provider extends React.Component{
  constructor(props) {
    super(props)
  }
  render () {
    return <ReactReduxContext.Provider value={{ store: this.props.store }}>
      {this.props.children}
    </ReactReduxContext.Provider>
  }
}
export default Provider

The connect method receives two parameters mapstatetoprops and mapdispatchtoprops, and returns a function. The returned function receives custom components (such as counter1). After the function is executed, it returns the final container component

// src/react-redux/connect.js
import React from 'react'
import {bindActionCreators} from 'redux'
import {ReactReduxContext} from './Context'

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    //Returns the final container component
     return class extends React.Component{
        static contextType = ReactReduxContext
        constructor(props, context){
          super(props)
          this.state = mapStateToProps(context.store.getState())
        }
        shouldComponentUpdate() {
          if (this.state === mapStateToProps(this.context.store.getState())) {
            return false;
          }
          return true;
        }
        componentDidMount () {
          this.unsubscribe = this.context.subscribe(() => {
            this.setState(mapStateToProps(this.context.store.getState()))
          })
        }
        componentWillUnmount (){
          this.unsubscribe && this.unsubscribe()
        }
        render(){
          const actions = bindActionCreators(
            mapDispatchToProps,
            this.context.store.dispatch
          )
          return <WrappedComponent {...this.state} {...this.props} {...actions}/>
       }
     }
   }
}

export default connect

It can be seen that in the connect method, bindactioncreators bind action and store Dispatch, there are state changes in the subscription store. We only use Redux and need to write them manually in the react component. Fortunately, react Redux helps us now

3、 Summary

Through the above sharing, we finally know why both Redux and react Redux need to be introduced into react applications