Redux has recently studied the source code and remains small and focused as part of the React stack. There are only a few hundred lines of code, which includes state machines, publish-subscribe patterns, and higher-order functions, and it’s certainly worth learning from. (The following source slightly modified and annotated)

1. Three Principles

Single data source

The state of the entire application is stored in an object tree, and this object tree is unique

State is read-only

The only way to change state is to fire an action, which is a normal object with an event

Use pure functions to perform modifications

To describe how an action changes the state tree, we need to reducer

Source directory

util/isPlainObject

// Check whether it is a pure object, not a constructorexport default function isPlainObject(obj) {
    if(typeof obj ! ='object' || obj === null) {
        return false;
    }
    letproto = obj; // get the obj final _proto_ pointerwhile(Object.getPrototypeOf(proto)) {
        proto = Object.getPrototypeOf(proto)
    }
    return Object.getPrototypeOf(obj) === proto;
}
Copy the code

This pure object determination determines whether the initial _proto_ reference and the final _proto_ reference of the object are the same

util/actionTypes.js

const ActionTypes = {
    INIT: '@@redux/INIT'
}
export default ActionTypes;
Copy the code

createStore.js

As the C bit in REdux, the methods include getState, Dispatch, subscribe methods

export default function createStore(reducer, preloadedState) {
    if(typeof reducer ! ='function') {
        throw new Error('Reducer is not a function')}letcurrentReducer = reducer; // State handlerletcurrentState = preloadedState; // Initial stateletcurrentListeners = []; // Listen on the function queuefunction getState() {
        return currentState;
    }

    function dispatch(action) {
        if(! isPlainObject(action)) { throw new Error('Action is not pure object')}if(typeof action.type == 'undefined') {
            throw new Error('Action type undefined'CurrentState = createReducer(currentState, action); // Publish subscribe modefor(let i=0; i<currentListeners.length; i++) {
            const listener = currentListeners[i];
            listener();
        }
        returnaction; } / / subscribefunction subscribe(listener) {
        let subscribed = true; currentListeners.push(listener); // Unsubscribereturn function unsubscribe() {// Repeat subscription optimizationif(! subscribed) {return ;
            }
            const index = currentListeners.indexOf(listener);
            currentListeners.splice(index, 1);
            subscirbed = false; }} // create store initialization value dispatch({type:ActionTypes.INIT});

    return{getState,// return status dispatch,// dispatch action subscirbe}}Copy the code

combineReducers.js

Merge multiple reducer into one object for easy maintenance

export default function(reducers) {// Return an array of reducers names const reducerKeys = object.keys (reducers);return function (state = {}, action) {
        const nextState = {}; 
        for(leti = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i]; const reducer = reducers[key]; const previousStateForKey = state[key]; // Old state const nextStateForKey = Reducer (previousStateForKey, action); // New state nextState[key] = nextStateForKey; }returnnextState; }}Copy the code

bindActionCreators.js

Integrate dispatches into actions

function bindActionCreator(actionCreator, dispatch) {
    return function() {
        returndispatch(actionCreator.apply(this, The arguments)}} / * * * * @ param {Function | Object} actionCreators * @ param {Function} dispatch of redux Store * effective dispatch method @ returns {Function | Object} if return objects similar to the original Object, but each Object element to add the * / dispatch methodexport default function bindActionCreators(actionCreators, dispatch) {
    if(typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
    }
    if(typeof actionCreators ! = ='object' || actionCreators === null) {
        throw new Error(
            `bindShould ActionCreators be an object or function type and not receive ${ActionCreators === null?'null': Typeof actionCreators}. '+' is not quoted as such"import ActionCreators from"Rather than"import * as ActionCreators from"? ')} // Const boundActionCreators = {}for (const key in actionCreators) {
        const actionCreator = actionCreators[key]
        if (typeof actionCreator === 'function') {
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
    }
    return boundActionCreators
}
Copy the code

compose.js

In this part, reduce method and high-order function are flexibly applied to realize the execution of parameter function from right to left

/** * * @param {... Any} funcs * @returns contains function arguments executed from right to left. For example, compose(f, g, h) equals (... args) => f(g(h(... args))) */export default functioncompose(... funcs) {if(funcs.length === 0) {
        return arg => arg
    }
    if(funcs.length === 1) {
        return funcs[0]
    }

    returnfuncs.reduce((a, b) => (... args) => a(b(... args))) }Copy the code

applyMiddleware.js

import compose from './compose'

export default functionapplyMiddleware(... middlewares) {returncreateStore => (... args) => { const store = createStore(... args)let dispatch = () => {
            throw new Error('Do not run dispatches while constructing middle keys, otherwise other middle keys will not receive dispatches') } const middlewareAPI = { getState: store.getState, dispatch: (... args) => dispatch(... args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch)return {
            ...store,
            dispatch
        }
    }
}
Copy the code

index.js

import createStore from './createStore';
import combineReducers from './combineReducers';
import bindActionCreators from './bindActionCreators';
import applyMiddleware from './applyMiddleware';
exportCreateStore,// Create warehouse combineReducers,// merge reducersbindActionCreators,// Bind the actionCreator and Dispatch methods to applyMiddleware}Copy the code

Intermediate key Practice

Redux-thunk middle key implementation

function createThunkMiddleware(extraArgument) {
    return ({dispatch, getState}) => next => action => {
        if(typeof action === 'function') {
            return action(dispatch, getState, extraArgument)
        } else {
            next(action)
        }
    }
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
Copy the code

redux-promise

// Promise: YesthenMethodfunction isPromise(obj) {
    return! obj&&(typeof obj =='object' || typeof obj == 'function') && (typeof obj.then == 'function')}export default function({dispatch, getState}) {
    return next => action => {
        returnisPromise(action.payload)? action.payload.then(result => { dispatch({... action, payload: result}) }).catch((error) => { dispatch({... action, payload:error, error:true});
            return Promise.reject(error)
        }) : next(action)
    }
}
Copy the code

The React – Redux implementation

connect.js

import React, {Component} from 'react';
import { bindActionCreators } from '.. /redux';
import ReduxContext from './context';

export default function(mapStateToProps, mapDispatchToProps) {
    return function(WrappedComponent) {
        return class extends Component {
            static contextType = ReduxContext;
            constructor(props, context) {
                super(props);
                this.state = mapStateToProps(context.store.getState());
            }
            componentDidMount() {
                this.unsubscribe = this.context.store.subscribe(() => {
                    this.setState(mapStateToProps(this.context.store.getState()))
                })
            }
            componentWillMount() {
                this.unsubscribe()
            }
            render() {
                let actions = {}
                if(typeof mapDispatchToProps == 'function') {
                    actions = mapDispatchToProps(this.context.store.dispatch)
                } else {
                    actions = bindActionCreators(mapDispatchToProps, this.context.store.dispatch);
                }

                return<WrappedComponent dispatch={this.context.store.dispatch} {... this.state} {... actions} /> } } } }Copy the code

Provider.js

import React, { Component } from 'react';
import ReduxContext from './context';
export default class Provider extends Component {
    render() {
        return (
            <ReduxContext.Provider value ={{store: this.props.state}}>
                {this.props.children}
            </ReduxContext.Provider>
        )
    }
}
Copy the code

context.js

import React from 'react';
const ReduxContext = React.createContext(null);
export default ReduxContext;
Copy the code

index.js

import Provider from './Provider';
import connect from './connect';
export {
    Provider,
    connect
}
Copy the code