For notes, refer to “React and Redux” by Cheng Mo. Understand the Flux framework before reading

Redux framework

The principle of story

The basic principle of Flux is “one-way data flow”, on which Redux emphasizes three basic principles:

  • Unique data source

    Unique data source means that the application’s state data should be stored in only one Store. The state on the Store is a tree object, and each component uses only a portion of the tree object’s data. How to structure the state on the Store is the core problem of Redux application

  • Keep status read-only

    Keep the state read-only, that is, you cannot change the state directly. To change the state of the Store, you must distribute an Action object. The way to change the state is not to modify the value of the state, but to create a new state object and return it to Redux, who then completes the assembly of the new state.

  • Data changes can only be done with pure functions

    The pure function described here is Reducer, and the first three letters of Redux, Red, represent Reducer. According to creator Dan Abramov, Redux’s name means Reducer+Flux

    reducer(state,action)
    Copy the code

    The first parameter state is the current state, the second parameter action is the received action object, and the reducer function needs to produce a new object return based on the state and action values. Note that reducer must be a pure function. That is, the return of the function must be entirely determined by the state and action parameters, without any side effects, and without modifying the state and action objects

    function reducer (state,action) = >{
        const {counterCaption}=action
        switch(action.type){
            case ActionTypes.INCREMENT:
                return{... state,[counterCaption]:state[counterCaption]+1}
            case ActionTypes.DECREMENT:
                return{... state,[counterCaption]:state[counterCaption]-1}
            default:
            	return state
        }
    }
    Copy the code

Redux sample

The sample

The basic directories are as follows:

Effect:

The sample source code



The installation

npm i --save redux
Copy the code

Action

Like Flux, Redux applications customarily split the action type and action constructor into two file definitions

SRC/ActionTypes. Js:

export const INCREMENT='increment'
export const DECREMENT='decrement'
Copy the code

SRC/Actions. Js:

import * as ActionTypes from './ActionTypes.js';

export const increament=(counterCaption) = >{
    return {
        type:ActionTypes.INCREMENT,
        counterCaption:counterCaption
    }
}
export const decrement=(counterCaption) = >{
    return {
        type:ActionTypes.DECREMENT,
        counterCaption:counterCaption
    }
}
Copy the code

Comparison: Each action constructor in Redux returns an Action object. In the Flux version, the Action constructor calls Dispatcher’s Dispatch function directly to dispatch the Action object. The difference between Redux and Flux is that redux is not distributed in an Actions file

/ / flux versions
import * as AcitonTypes from './ActionTypes';
import AppDispatcher from './AppDispatcher'

export const increment = (counterCaption) = >{
 AppDispatcher.dispatch({
     type:AcitonTypes.INCREMENT,
     counterCaption:counterCaption
 })
}
export const decrement = (counterCaption) = >{
 AppDispatcher.dispatch({
     type:AcitonTypes.DECREMENT,
     counterCaption:counterCaption
 })
}
Copy the code

Store

The createStore function provided by the Redux library: The first parameter represents the reducer of the update state, the second parameter is the initial value of the state, and the optional third parameter represents StoreEnhancer. The state is obtained by getStore()

src/Store.js

import {createStore} from 'redux'
import reducer from './Reducer.js'
const initValues={
    'First':0.'Second':10.'Third':20
}
const store=createStore(reducer,initValues)
export default store
Copy the code

Reducer

Used to update status. This is where any action dispatched through Dispatch changes the store state

src/Reducer.js

import * as ActionTypes from './ActionTypes.js'
export default (state,action)=>{
    const {counterCaption}=action
    switch(action.type){
        case ActionTypes.INCREMENT:
            return{... state,[counterCaption]:state[counterCaption]+1}
        case ActionTypes.DECREMENT:
            return{... state,[counterCaption]:state[counterCaption]-1}
        default:
        	return state
    }
}
Copy the code

Redux has one more parameter state than Flux, but there is no parameter state in Flux. The difference between Redux and Flux is that the state in Flux is managed by Store, while the state in Redux is managed by Redux itself.

/ / flux versions
CounterStore.dispatchToken=AppDispatcher.register((action) = >{
 if(action.type===AcitonTypes.INCREMENT){
     counterValues[action.counterCaption]++
     CounterStore.emitChange()
 }else if(action.type===AcitonTypes.DECREMENT){
     counterValues[action.counterCaption]--
     CounterStore.emitChange()
 }
})
Copy the code

View

src/views/ControlPanel.js

import React from 'react'
import Counter from './Counter'
import Summary from './Summary'

export default class ControlPanel extends React.Component{
    render(){
        return(
        	<div>
            	<Counter caption="First" />
                <Counter caption="Second" />
                <Counter caption="Third" />
                <hr/>
                <Summary/>
            </div>)}}Copy the code

src/views/Counter.js

import React from 'react'
//1. Introduce store and action
import store from '.. /Store.js'
import * as Actions from '.. /Actions'

export default class Counter extends React.Component{
    constructor(props) {
        super(props);
        this.onChange = this.onChange.bind(this);
        this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
        this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
        //3. Initialization status
        this.state = this.getOwnState()
    }

    //2. Return a corresponding state obtained from store
    getOwnState(){
        return {value:store.getState()[this.props.caption]}
    }
    
    //4. Keep store state in sync with this.state and call onChange whenever store changes
    Subscribe and unsubscribe are used to register and unsubscribe listeners, respectively
    componentDidMount(){
        store.subscribe(this.onChange)
    }
    componentWillUnmount(){
        store.unsubscribe(this.onChange)
    }   
    onChange(){
        this.setState(this.getOwnState())
    }

    //5. The only way to change the store state is to issue an action. Call the store.dispatch function to dispatch the action
    onClickIncrementButton(){
        store.dispatch(Actions.increment(this.props.caption))
    }
    onClickDecrementButton(){
        store.dispatch(Actions.decrement(this.props.caption))
    }
    
    render(){
        const {caption} =this.props
        const value=this.state.value
        return(
        	<div>
            	<input type="button" onClick={this.onClickIncrementButton} value="+"/>
                <input type="button" onClick={this.onClickDecrementButton} value="-"/>
                <span>{caption} count:{value}</span>
            </div>)}}Copy the code

src/views/Summary.js

import React from 'react'
import store from '.. /Store.js'

export default class Summary extends React.Component{
    constructor(props){
        super(props)
        this.onUpdate = this.onUpdate.bind(this);
        this.state=this.getOwnState.bind(this);
    }
    
    //1. Define a summation function that returns an object
    getOwnState(){
        const state=store.getState();
        let sum=0
        for(const key in state){
            if(state.hasOwnProperty(key)){
                sum+=state[key]
            }
        }
        return {sum:sum}
    }
    
    //2. Synchronization status
    componentDidMount(){
        this.setState(this.getOwnState())
        store.subscribe(this.onUpdate)
    }
    componentWillUnmount(){
        store.unsubscribe(this.onUpdate)
    }    
    onUpdate() {
        this.setState(this.getOwnState())
    }
    
    render(){
        return(
        	<div>
            	total:{this.state.sum}
            </div>)}}Copy the code

conclusion

  • Defining action types
  • Create the Action constructor
  • CreateStore with createStore — reducer, initial state, (StoreEnhancer optional)
  • Create reducer: Contains state and ation parameters
  • View partial initialization state: get the state from store.getStore()
  • Synchronizes states with subscribe and unsubscribe
  • Action is dispatched via store.dispatch to change the store state

Improved Redux(1) – Container components and presentation components

In the Redux framework, a React component basically performs two functions:

  • Interacts with the Redux Store, reads the state of the Store, initializes the component’s state, and listens for changes in the Store’s state. When Store state changes, component state needs to be updated to drive component re-rendering; When the Store state needs to be updated, the action object should be distributed.
  • Render the user interface based on the current props and state

If you find that a component is doing too much, you can split the component into multiple components so that each component still does only one thing:

  • The Component responsible for dealing with the Redux Store is the outer layer, called the Container Component.
  • The component responsible for rendering the interface, located in the inner layer, is called the Presentation Component.

Now modify the Counter component:

import React from 'react'
import store from '.. /Store.js'
import * as Actions from '.. /Actions'

//1. Container components
class CounterContainer extends React.Component{
    constructor(props) {
        super(props);
        this.onChange = this.onChange.bind(this);
        this.onIncremnet = this.onIncremnet.bind(this);
        this.onDecrement = this.onDecrement.bind(this);
        this.state = this.getOwnState()
    }
    getOwnState(){
        return {value:store.getState()[this.props.caption]}
    }
    
    // Synchronization status
    componentDidMount(){store.subscribe(this.onChange)}
    componentWillUnmount(){store.unsubscribe(this.onChange)}   
    onChange(){this.setState(this.getOwnState())}
    
    Distributed / / action
    onIncremnet(){
        store.dispatch(Actions.increment(this.props.caption))
    }
    onDecrement(){
        store.dispatch(Actions.decrement(this.props.caption))
    }
    
    render(){
        return <Counter caption={this.props.caption}
        onIncremnet={this.onIncremnet}
        onDecrement={this.onDecrement}
        value={this.state.value}
        />}}// Export container components by default
export default CounterContainer

    
    
//2
function Counter(props){
    const {caption,onIncremnet,onDecrement,value}=props
    return(
        <div>
            <input type="button" onClick={onIncremnet} value="+"/>
            <input type="button" onClick={onDecrement} value="-"/>
            <span>{caption} count:{value}</span>
        </div>)}//2
// function Counter({caption,onIncremnet,onDecrement,value}){
// return(
// 
      
// // // {caption} count:{value} // / /) // } Copy the code

The sample source code

Improved Redux(2) – Component Context

In both the Counter and Summary component files, store is imported. However, importing stores directly into a component is very detrimental to component reuse. In an application, there should be only one place to import the Store directly. This should be where the React component is called at the top level. In this case, this location is the entry file SRC/index.js for the application

Instead of importing a component directly into a Store, you have to have the component’s upper layer pass the Store down. However, there is a defect in the props that all components need to help deliver the props. React provides a function called Context that solves this problem perfectly.

src/Provider.js

import React from 'react'
class Provider extends React.Component{
    getChildContext(){
        return{
            store:this.props.store
        }
    }
    render(){
        return this.props.children
    }
}
Copy the code

Provider provides the function getChildContext, which holds the object representing the Context. In order for a Provider to be recognized as a Context Provider by React, you also need to specify the Provider’s childContextTypes property. The Provider defines the class childContextTypes, which must correspond to getChildContext so that the Provider’s children can access the context.

import PropTypes from 'prop-types';
Provider.childContextTypes={
    store:PropTypes.object
}
Copy the code

src/index.js

import store from './Store'
import Provider from './Provider'

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

src/views/Counter.js

  • Give CounterContainer contextTypes of a class assignment, should work with the Provider. ChildContextTypes values consistent, otherwise unable to access to the context

    import PropTypes from 'prop-types';
    CounterContainer.contextTypes = {
        store: PropTypes.object
    }
    Copy the code
  • All access to the store is done through this.context.store. (Same goes for SRC /views/ summary.js)

    componentDidMount(){this.context.store.subscribe(this.onChange)}
    Copy the code
  • Since the constructor is defined, the second argument context is used

    constructor(props,context) {
        super(props,context); . }Copy the code

    or

    constructor() {
        super(... arguments); . }Copy the code

    The sample source code

Improve Redux (3) – the React – story

The first is to split a component into container components and presentation components. The second is to use the React Context to provide a Context that all components can access directly. In fact, there is already a library that does this: React-Redux

npm install --save react-redux
Copy the code

React-redux has two main features:

  • Provider: provides the context containing the store

  • Connect: Connect container components and presentation components;


Provider

In SRC /index.js, react-redux lets you import your own Provider directly from SRC /index.js instead of using it:

import {Provider} from 'react-redux'
Copy the code

connect

Connect eliminates the need to define container components such as CounterContainer and now simply exports a statement like this:

import {connect} from 'react-redux'; .export default connect(mapStateToProps,mapDispatchToProps)(Counter)
Copy the code

Connect accepts two parameters mapStateToProps and mapDispatchToProps. The first execution is the connect function, the second execution is the connect return function (with the display component Counter), and the final result is the container component. The connect function does two things:

  • Convert state on Store to prop for inner presentation components

    / / mapStateToProps function:
    function mapStateToProps(state,ownProps){
        return {
            value:state[ownProps.caption]
            //ownProps is a prop object. OwnProps. Caption Reads the Caption property value of a prop object
            //state[ownProps. Caption] is equivalent to getting the store value of the caption property
            
             Get the First property in the store
            // Then the presentation component can get the corresponding value via props. Value}}Copy the code
  • Convert user actions in the inner presentation component into actions sent to the Store

    / / mapDispatchToProps function
    function mapDispatchToProps(dispatch,ownProps){
        return{
            onIncrement:() = >{
                dispatch(Actions.increment(ownProps.caption))
            },
            onDecrement:() = >{
                dispatch(Actions.decrement(ownProps.caption))
            }
        }
    }
    Copy the code

In contrast to the previous two improvements, the Counter component does not write the synchronization status section

Complete code:

//Counter.js
import * as Actions from '.. /Actions'
import {connect} from 'react-redux';


// Convert state on Store to prop for inner presentation components
function mapStateToProps(state,ownProps){
    return {
        value:state[ownProps.caption]
    }
}

// Convert user actions in the inner presentation component to actions sent to the Store
function mapDispatchToProps(dispatch,ownProps){
    return{
        onIncrement:() = >{
            dispatch(Actions.increment(ownProps.caption))
        },
        onDecrement:() = >{
            dispatch(Actions.decrement(ownProps.caption))
        }
    }
}
// Export the container component
export default connect(mapStateToProps,mapDispatchToProps)(Counter)

// Display the component
function Counter(props){
    const {caption,onIncrement,onDecrement,value}=props
    return(
        <div>
            <input type="button" onClick={onIncrement} value="+"/>
            <input type="button" onClick={onDecrement} value="-"/>
            <span>{caption} count:{value}</span>
        </div>)}Copy the code
//Summary.js
import {connect} from 'react-redux'

// Convert state on Store to prop for inner presentation components
function mapStateToProps(state) {
    let sum = 0;
    for (const key in state) {
      if(state.hasOwnProperty(key)) { sum += state[key]; }}return {value: sum};
}

function Summary({value}) {
    return (
      <div>Total Count: {value}</div>
    );
}

export default connect(mapStateToProps)(Summary);
Copy the code
//index.js
import store from './Store'
import {Provider} from 'react-redux'

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

The sample source code

conclusion

  • Defining action types

  • Create the Action constructor export object

  • CreateStore with createStore — reducer, initial state, (StoreEnhancer optional)

  • Create reducer: Contains state and ation parameters

  • Import the Provider component from the React-redux to provide the context so that the store can be accessed anywhere in the component without importing the store

  • Import the Connect method from react-Redux, accept two functions, a presentation component, and finally generate a container component. One function converts the state on the Store into a prop that displays the component; Another function converts user actions in the presentation component into actions sent to the Store