This article mainly refer to the post: 8 k word | Redux/react – story/Redux middleware design analysis

In order to facilitate my reading, I summarized the above articles and modified some interface implementations based on the latest React. If you are interested, you can read above directly

Why redux

Manage common state to facilitate communication between components

Design and implementation of REdux

design

To achieve state management, there are mainly the following steps

  • State variablescurrentState
  • The storage and retrieval of states, i.esetter,gettermethods
  • When the state changes and the data is broadcast, you need to havesubscribemethods

So the store looks like this

const createStore = () = > {
    let currentState = {};     // Public state

    function getState() {}     // getter
    function dispatch() {}     // setter    
    function subscribe() {}    // Publish a subscription

    return { getState, dispatch, subscribe };
}
Copy the code

The implementation of getter methods

Return current state

const createStore = () = > {
    // ...
    
    function getState () {
        returncurrentState; }}Copy the code

The implementation of the Dispatch method

Pass in an action object that modifies the store’s data conditionally and named

// Follow the reducer approach
const initialState = {
    count: 0
};

function Reducer(state = initialState, action) {
    switch(action.type) {
        case 'plus':
            return {
                ...state, 
                count: state.count + 1
            }
        case 'substract':
            return {
                ...state,
                count: state.count - 1
            }
        default:
            returninitialState; }}const createStore = () = > {
    // ...
    
    function dispatch(action) {
        currentState = reducer(currentState, action)
    }	
}
Copy the code

Subscribe method implementation

Refer to the observer-subscriber pattern as follows

/ / observer
class Observer {
    constructor(fn) {
        this.update = fn; }}// Observed
class Subject {
    constructor() {
        this.observers = [];
    }

    addObserver(observer) {
        this.observers.push(observer);
    }

    notify() {
        this.observers.forEach(observer= >{ observer.update(); }}})var subject = new Subject();
var observer1 = new Observer(() = > { console.log(1); });
var observer2 = new Observer(() = > { console.log(2); });
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notify();

/ / 1
/ / 2
Copy the code

Based on the above pattern, we can realize the broadcast update of data when it is updated


const createStore = () = > {
    // ...

    
    let observers = [];    // Observer queue, used to broadcast updates
    
    function dispatch(action) {
        currentState = reducer(currentState, action);
        observers.forEach(fn= > fn());    Execute the methods in the observer queue in turn
    }

    function subscribe(fn) { observers.push(fn); }}Copy the code

The final code

// Follow the reducer approach
const initialState = {
    count: 0
};

function reducer(state = initialState, action) {
    switch (action.type) {
        case 'plus':
            return {
                ...state,
                count: state.count + 1
            }
        case 'substract':
            return {
                ...state,
                count: state.count - 1
            }
        default:
            return initialState
    }
}

const createStore = (reducer) = > {
    // Public state
    let currentState = {};  
    
    / / observer
    let observers = [];   

    // getter
    function getState() { 
        return currentState;
    }     

    // setter   
    function dispatch(action) {   
        currentState = reducer(currentState, action);
        observers.forEach(fn= > fn());    // When data changes, the corresponding method is triggered
    }

    // Publish a subscription
    function subscribe(fn) {
        observers.push(fn);
    }

    // Initialize parameters
    dispatch({type: ' '})

    return { getState, dispatch, subscribe };
}


/ / use
const store = createStore(reducer);
store.subscribe(() = > { console.log(1); });
store.subscribe(() = > { console.log(2); });
store.dispatch({type: 'plus'})


/ / 1
/ / 2
Copy the code

Design and implementation of React-Redux

Provider and Connect apis are provided

  • ProviderWill:storeWith the help ofReact ContextData transfer
  • connectWill:getState,dispatchMethods merge intoprops, passed to the component and implemented automatic subscription updates

Implementation of the Provider method

const ReduxContext = React.createContext(null);

class Provider extends React.Component {
    render() {
        return (
            <ReduxContext.Provider value={this.props.store}>
                {this.props.children}
            </ReduxContext.Provider>)}}Copy the code

Implementation of connect method

connect(mapStateToProps, mapDispatchToProps)(App)   // Higher-order components
Copy the code

Connect accepts two parameters

  • mapStateToProps
    • willstoreIs converted toUIA variable with the same name in a component
  • mapDispatchToProps
    • To establishUIThe component andstore.dispatchMethod mapping

React is implemented with higher-order components

function connect(mapStateToProps, mapDispatchToProps) {
    return function (Component) {
        class Connect extends React.Component {
            componentDidMount() {
                this.context.subscribe(this.handleStoreChange.bind(this));
            }

            handleStoreChange() {
                Component updates can also be triggered by setState, etc
                this.forceUpdate();
            }

            render() {
                return (
                    <Component
                        {. this.props}
                        {. mapStateToProps(this.context.getState()} / / shiftstroeIn {. mapDispatchToProps(this.context.dispatch} // Create andstore.dispatchMethod mapping />
                )
            }
        }

        Connect.contextType = ReduxContext;

        returnConnect; }}Copy the code

Simple and easy to use

Following the Redux example, use the interface above

function mapStateToProps(state) {
    return {
        count: state.count
    };
}

// Define the corresponding action
const addCountAction = {
    type: 'plus'
}

function mapDispatchToProps(dispatch) {
    return {
        addCount: () = >{ dispatch(addCountAction); }}}class Test extends React.Component {
    render() {
      // console.log(this.props);
        return (
            <div>
                {this.props.count}
                <button onClick={()= > this.props.addCount()}>Add</button>
            </div>)}}const App = connect(mapStateToProps, mapDispatchToProps)(Test);

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

Redux Middleware implementation

Middleware is used to intercept the process of dispatch to reducer, so as to enhance dispatch function

Implementation approach

Modify Dispatch to enhance createStore functionality

A modified createStore

In the presence of heightener, the enhanced createStore is received directly through heightener

const createStore = (reducer, heightener) = > {
    if(heightener) {
        heightener(createStore)(reducer);
    }

    // ...
}
Copy the code

Define applyMiddleware middleware functions

In other words, heightener entry parameter functions

const applyMiddleware = (. middlewares) = > createStore= > reducer= > {
    const store = createStore(reducer);
    let { getState, dispatch } = store;   // Get the store getState, dispatch method

    const params = {
        getState,
        dispatch: (action) = > dispatch(action)
    };

    const middlewareArr = middlewares.map(middleware= >middleware(params)); dispatch = compose(... middlewareArr)(dispatch);return { ...store, dispatch };
}

function compose(. fns) {
    if (fns.length === 0) return arg= > arg;
    if (fns.length === 1) return fns[0];
    return fns.reduce((res, cur) = > (. args) = >res(cur(... args))); }Copy the code

Simple to use

const logger = store= > next= > action= > {    
    console.log('log1'); 
    let result = next(action);
    return result;
}

const thunk = store= > next= > action= > {
    console.log('thunk');
    return typeof action === 'function' ?  action(store.dispatch) : next(action);
}

const logger2 = store= > next= > action= > {    
    console.log('log2'); 
    let result = next(action); 
    return result;
}

let store = createStore(reducer, applyMiddleware(logger, thunk, logger2))

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

Code warehouse

Gitee.com/hyy215/reac…