Analysis: let you understand the principle of redux

redux analysis

Author: HerryLo

A permanent link to this article: https://github.com/AttemptWeb

Redux is a JavaScript state container that provides predictable state management.

In actual development, it is often used with react + react redux. This represents a basic concept of front-end development, the separation of data and view.

redux came into being. Of course, there are other state management libraries, such as Flux and Elm. Of course, we only analyze redux here.

redux create Store

To create the store object of redux, you need to call the combineReducers and createStore functions. The following explanation does not include middleware.

const reducer = combineReducers({
    home: homeNumber,
    number: addNumber
})
const store = createStore(reducer)
// It is temporarily mounted under the window, and will be used to
window.$reduxStore = store

combineReducers function

First, call the combinedreducers function and pass in multiple reducers as parameters. The source code is as follows:

// reducers are the passed in parameter objects
function combineReducers(reducers) {
    // Omit...
    return function combination(state = {}, action) {
        let hasChanged = false
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
            // finalReducerKeys is the key value of the passed in reducers object
            const key = finalReducerKeys[i]
            // Final reducers are equivalent to reducers
            const reducer = finalReducers[key]
            const previousStateForKey = state[key]
            // Run the reducer function to return a state
            // Core: call the combination function, which is actually a circular call to the incoming reducer function
            const nextStateForKey = reducer(previousStateForKey, action)

            nextState[key] = nextStateForKey
            // hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        // hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
        // Return state object
        return nextState
    }
}
// Source address: https://github.com/reduxjs/redux/blob/master/src/combinedreducers.tsාl139

The above code is actually very simple. The combineReducers function runs and returns a new combination function. The main function of the combination function is to return an object with all state attached When the combination function is called, it is actually a circular call to the incoming reducer function, returning the state object. Pass the combination function as an argument into the createStore function.

createStore function

function createStore(reducer, preloadedState, enhancer) {
    // Reducer - > combination function
    let currentReducer = reducer
    // All state properties, mounted on currentState
    let currentState = preloadedState

    // The following middleware will use
    if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        // The second parameter is a function without the third parameter
        enhancer = preloadedState
        // Reset preloadedState
        preloadedState = undefined
    }
    if (typeof enhancer !== 'undefined') {
        // When there is middleware, the createStore is passed into the middleware function, and the enhancer function is called, and the return ends.
        return enhancer(createStore)(reducer, preloadedState)
    }

    function dispatch(action) {
        // Currentreducer -- > combination function
        currentState = currentReducer(currentState, action)
    }

    // Initialize calling dispatch to create initial state
    dispatch({ type: ActionTypes.INIT })

    const store = ({
        dispatch: dispatch,
        subscribe,s
        getState,
        replaceReducer,
        [$$observable]: observable
    }
    return store
}
// Source address: https://github.com/reduxjs/redux/blob/master/src/createstore.ts × L60

reducer is the incoming combination function, preloadedState is the initialized state (not too much effect), enhancer is the middleware, and in the case of no third parameter enhancer, the second parameter preloadedState is a function, and preloadedState is assigned to enhancer.

Call the dispatch function to initialize. currentReducer is the incoming combination function. As mentioned above, calling the combination function is actually a circular call to the reducer function. All state objects are mounted on the internal variable currentState. When there is a middleware enhancer, pass createStore into the middleware function, call enhancer function, and return is finished, which will be discussed later.

For the created store object, the methods exposed are as follows:

const store = ({
    // Distribute the action, which is the only way to trigger a state change.
    dispatch: dispatch as Dispatch<A>,
    // Change monitor
    subscribe,
    // Get all the state s under the store
    getState,
    // Replace the reducer currently used by the store to calculate the state
    replaceReducer
}
return store

The dispatch function triggers the action, calls the reducer function, and modifies the state. The subscribe function listens for changes in state. The getState function gets all the States. The replaceReducer function replaces the reducer function used to calculate the state.

Combine the reducer function through the combinedreducers function, and return a new function combination (this function is responsible for looping through and running the reducer function, and returning all States). The new function is passed into the createStore function as a parameter, and the internal function initializes and runs the incoming combination and state generation through dispatch, returning the store object

redux Middleware

It's better to read the above and then read the middleware part!! The middleware is analyzed as follows:

redux thunk is only one kind of redux middleware, and it is also a common middleware. The redux thunk library allows you to write asynchronous logic that interacts with the store.

import thunkMiddleware from 'redux-thunk'
const reducer = combineReducers({
    home: homeNumber,
    number: addNumber
})

const store = createStore(
    reducer,
    applyMiddleware(
        thunkMiddleware, // Asynchronous Support
    )
)

The createStore function supports three parameters. If the second parameter preloadedState is a function and there is no third parameter enhancer, the preloadedState will be assigned to enhancer.

The following will take the Redux thunk middleware as an example. Here is the code of thunkMiddleware function:

// Part of the code is changed to ES5 code. Running middleware function will return a new function, as follows:
return ({ dispatch, getState }) => {
    // next is actually the incoming dispatch
    return function (next) {
        return function (action) {
            // Redux thunk core
            if (typeof action === 'function') { 
                return action(dispatch, getState, extraArgument);
            }
            return next(action);
        };
    };
}
// Source address: https://github.com/redux js/redux-thunk/blob/master/src/index.js

The internal source code of Redux thunk library is very simple, GitHub: Redux thunk source code , which allows action to be a function and supports parameter passing. Otherwise, the calling method does not change.

applyMiddleware function

// Middleware call
return enhancer(createStore)(reducer, preloadedState)

//Equivalent to

return applyMiddleware(
    thunkMiddleware,
)(createStore)(reducer, preloadedState)

redux middleware starts from applyMiddleware function. Its main purpose is to handle the dispatch function of store.

// Support multiple middleware incoming
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, ...args) => {
    // Create store
    const store = createStore(reducer, ...args)

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    // Traverse and run the middleware functions, passing the middleware API as a parameter
    // Middleware corresponds to the core code of Redux thunk Library above. Of course, it also supports multiple middleware
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // Core: pass all middleware into compose and return a new dispatch
    dispatch = compose(...chain)(store.dispatch)
    // Return a store object as usual. The dispatch has been processed
    return {
      ...store,
      dispatch
    }
  }
}
// Source address: https://github.com/reduxjs/redux/blob/master/src/applymiddleware.ts × L55

The applyMiddleware function takes multiple middlewares parameters and returns a store object. Create the store object through createStore, mount the getState and dispatch on the middleware API object, loop the middlewares middleware, and pass the middleware API as a parameter to each middleware. After the traversal, an array containing all the new return functions of middleware is obtained and assigned to the variable chain.

// The value of chain after traversal. Here is just the Redux thunk library as an example
// next is dispatch
chain = [function (next) {
    return function (action) {
        if (typeof action === 'function') { // Redux thunk core
            return action(dispatch, getState, extraArgument);
        }
        return next(action);
    };
}, ...More Middleware]

compose function

// Array chain holds all new return functions of Middleware
dispatch = compose(...chain)(store.dispatch)

The main function of compose is to return a processed dispatch function after running all middleware functions.

// compose function
return chain.reduce((a, b) =>{
    return (...args)=> {
        return a(b(...args))
    }
}

chain is an array for saving middleware functions. For the specific internal structure, please refer to 👆 above. Next, analyze the call logic of compose function.

// chain analogy is [fn1, fn2, fn3, fn4]
[fn1, fn2, fn3, fn4].reduce((a, b) =>{
    return (...args)=> {
        return a(b(...args))
    }
}

The procedure is as follows:

loop a value b value Returned value
First cycle fn1 fn2 (...args)=> fn1(fn2(...args))
Second cycle (...args)=> fn1(fn2(...args)) fn3 (...args)=> fn1(fn2(fn3(...args)))
Third cycle (...args)=> fn1(fn2(fn3(...args))) fn4 (...args)=> fn1(fn2(fn3(fn4(...args))))

After being processed by compose, the final return value is (... Args) = > FN1 (FN2 (FN3 (fn4 (... Args))), and this arg is the store.dispatch function. Finally, assign the return function to dispatch, which is the dispatch function we need. If there is only one middleware, it will return directly.

The main purpose of applyMiddleware function middleware is to modify the dispatch function and return the new dispatch function processed by middleware

redux use

The use here does not match react Redux + react;

window.$reduxStore = store

store.dispatch(action);

let { aState } = store.getState()

The above is to mount it directly under the window object, so that it can be used with any front-end framework. Of course, this is certainly not elegant. I will talk about a special article that is used with react Redux later;

In this case, the store.dispatch function is called. In fact, the loop traversal function is called again to call the reducer function. After the update, it is saved on the currentState internal variable of the createStore function. Through the store.getState function, return the currentState variable to get all the States.

End:

There are a lot of contents, so I need to summarize them

1. Create store with Redux: merge the reducer function through the combinedreducers function, and return a new function combination (this function is responsible for looping through and running the reducer function, and returning all States). The new function is passed into the createStore function as a parameter, and the internal function initializes and runs the incoming combination and state generation through dispatch to return the store object.

2.redux middleware: the main purpose of applyMiddleware function middleware is to modify the dispatch function and return the new dispatch function processed by middleware

3. Use of Redux: in fact, call the loop traversal again and call the reducer function to update the state

This is the end of the analysis. redux is really very functional, full of functional programming ideas, very modular, and has a strong versatility. I think it's very good

ps: WeChat public address: Yopai, interested, can be concerned, updated weekly, sharing can increase the happiness of the world.

Tags: Javascript github React Programming

Posted on Mon, 04 Nov 2019 19:20:36 -0800 by Houdini