react – Implementation of Redux

Time:2021-4-21

Provider component

Provider is used to establish global attributes that can be accessed by subcomponents. There are two core APIs

  • The childcontexttypes static property is used to specify the global property type accessed by the child component
  • The getchildcontext method is used to specify global properties that can be accessed by child components

Provider short version source code implementation:

import React from 'react';
import PropTypes from 'prop-types';

export default class Provider extends React.Component{

    // Specifies the types of properties that the subcomponent can access
    static childContextTypes = {
        store: PropTypes.object
    }

    constructor(props){
        super(props);
    }

    //Specifies the properties that the subcomponent can access
    getChildContext(){
        return{
            store: this.props.store
        }
    }

    render() {
        // Render the view with the subcomponents it contains
        return this.props.children;
    }
}

Then, specify contexttypes to obtain the specified global attribute among the sub components referenced to the global attribute. At the same time, you need to declare context in the constructor of the sub component, otherwise the context is an empty object

static contextTypes = {
    store: PropTypes.object
}
    
constructor(props,context){
    super(props,context);
}

Finally, take the provider component as the root component and pass in the corresponding global attribute

<Provider store={store}>
    <App></App>
</Provider>

Connect function

In fact, the connect function is a high-level component in proxy mode, which enhances or weakens the target component to form a new component, and then returns the new component.

The connect function has two parameters, mapstatetoprops and mapdispatchtoprops.

mapStateToProps

Mapstatetoprops is an anonymous function, which is used to specify the props passed to the component. The implementation method is simple. You can directly execute its anonymous function

const stateProps = mapStateToProps(store.getState());

Finally, expand stateprops and pass it to the target component

mapDispatchToProps

Mapdispatchtoprops can be either an object or a function. It is mainly used to associate an action with the passed props. The bindactioncreators function is used to encapsulate the action and change it into a dispatch (action). The specified action is distributed to call the specified props.

const dispatchProps = bindActionCreators(mapDispatchToProps,store.dispatch);

// Encapsulating action as dispatch ( action )
function bindActionCreators(actionCreators,dispatch) {
    return Object.keys(actionCreators).reduce((result,item) => {
        result[item] = (...args) => dispatch(actionCreators[item](...args));
        return result;
    },{})
}

Finally, expand the dispatchprops and pass it to the target component

Complete implementation of connect function

import React from 'react';
import PropTypes from 'prop-types';
import {bindActionCreators} from './mini-redux';

export const connect = (mapStateToProps = state => state, mapDispatchToProps={}) => (WrapComponent) => {
    class ConnectComponent extends React.Component{

        //Specifies the global property to access. If contexttypes is not defined, the context will be an empty object
        static contextTypes = {
            store: PropTypes.object
        }

        constructor(props,context){
            super(props,context);
            this.state = {
                props: {}
            }
            this.update = this.update.bind(this);
        }

        componentDidMount(){
            const {store} = this.context;
            store . subscribe ( this . update );// Subscribe to the status in the store and call the update function when it changes
            this.update();
        }

        componentWillUnmount(){
            const {store} = this.context;
            store.unsubscribe ( this.update ); // remove the function that monitors the state change of the stor
        }

        // Get mapstatetoprops and mapdispatchtoprops and put them in this . state . Update components in props
        update(){
            const {store} = this.context;
            const stateProps = mapStateToProps( store.getState ()); // get the passed in state
            const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch ); // encapsulate action as dispatch (action)
            this.setState({
                props:{
                    ...this.state.props,
                    ...stateProps,
                    ...dispatchProps
                }
            })
        }

        render(){
            return(
                // Pass in the state and encapsulate the dispatch ( action ) Pass to target component as props
                <WrapComponent {...this.state.props}></WrapComponent>
            )
        }
    }

    return ConnectComponent;
}

middleware

Middleware uses applymiddleware to enhance the function of createstore, which is similar to decorator mode. Let’s take a look at the simplified implementation of the function of createstore

Createstore function

export function createStore(reducer,enhancer) {
    if(enhancer){
        //If there is a store enhancer, the createstore is encapsulated
        return enhancer(createStore)(reducer);
    }
    Let currentstate = {}; // saves the current state of the project, which is empty by default
    let currentListeners  = [];// A function used to store changes in the monitor store

    //Used to get the current state
    function getState() {
        return currentState;
    }

    // Distribute action
    function dispatch(action) {
        Currentstate = reducer (currentstate, action); // use reducer to process the current distributed action
        currentListeners . forEach ( currentListener  =>  currentListener ());// After processing, it returns a new state to trigger the change of the store, traverses the function that monitors the change of the store, and executes it to make the component change
        return action;
    }

    //Add the function of monitoring store changes
    function subscribe(listener) {
        currentListeners.push(listener);
    }

    //Remove functions that unsubscribe for store changes
    function unsubscribe(listener) {
        currentListeners = currentListeners.filter(l => l !== listener)
    }

    Dispatch ({type: 'xxxxx'}); // trigger the initial call, type can be set to any value, but the default case must be used to get the default value and pass it to the component initialization
    Return {getstate, dispatch, subscribe, unsubscribe}; // returns three functions inside the store for external call
}

Applymiddleware function

As you can see above, there is a store enhancer, which is actually the applymiddleware function. It returns two layers of nested functions, mainly to enhance the dispatch method of the store, so that the dispatch action does not directly arrive at the reducer function for processing, but goes through layers of middleware. Therefore, the brief version of applymiddleware function is implemented as follows:

function applyMiddleWare(...middlewares) {
    return createStore => (...args) => {
        Const store = createstore (... Args); // create native store according to reducer and initial value
        let dispatch =  store.dispatch ; // get the dispatch function of the native store
        //Middleware is a function nested with two layers of functions, which receives getstate and dispatch as parameters
        const middlewareApi = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args)
        }
        const middlewareChain =  middlewares.map (middleware = > middleware (middlewareapi)); // get the middleware passed in, and initialize the middleware function
        dispatch = compose(...middlewareChain)( store.dispatch ); // combine multiple middleware, and put the native dispatch as the dispatch parameter of the last middleware
        //The method of single middleware: dispatch = middleware (middleware API)( store.dispatch );
        // Returns the store of the enhanced dispatch function
        return{
            ...store,
            dispatch
        }
    }
}

Compose function

The compose function is to combine multiple middleware and convert compose (FN1, FN2, FN3) into FN1 (FN2 (FN3)). The function is executed from right to left. In fact, the execution result of the next function is used as the parameter of the previous function. Correspondingly, the next middleware is used as the next parameter of the previous intermediate function. The simplified source code of compose is as follows:

function compose(...fns){
    if(fns.length === 0){
        return arg => arg
    }
    else if(fns.length === 1){
        return fns[0]
    }
    else{
        //Return result: (... Args) = > FN1 (FN2 (FN3 (... Args)))
        return fns.reduce((result,item) => (...args) => result(item(...args)));
    }
}

Implementation of short version of chunk Middleware

const thunk = ({dispatch,getState}) => next => action => {
    //If action is a function, the function is executed
    if(typeof action === 'function'){
        return action(dispatch,getState);
    }
    //Otherwise, if the current middleware is not the last one, the action will be passed to the next middleware for execution
    //If the current middleware is the last middleware, then next is the native dispatch on the store, which is directly processed by the reducer
    return next(action);
}

export default thunk;

Custom Middleware

We try to customize a middleware arraythunk manually to determine whether the action is an array type. If it is an array type, we traverse the array and redistribute the elements in the array as actions

const arrayThunk = ({dispatch,getState}) => next => action => {
    if(Array.isArray(action)){
        return action.forEach(item => dispatch(item));
    }
    return next(action);
}

export default arrayThunk;

Call normally

const store = createStore(count,applyMiddleWare(thunk,arrayThunk));

Recommended Today

Looking for frustration 1.0

I believe you have a basic understanding of trust in yesterday’s article. Today we will give a complete introduction to trust. Why choose rust It’s a language that gives everyone the ability to build reliable and efficient software. You can’t write unsafe code here (unsafe block is not in the scope of discussion). Most of […]