解析: 讓你弄懂redux原理

做者: HerryLohtml

本文永久有效連接: https://github.com/AttemptWeb......前端

Redux是JavaScript狀態容器,提供可預測化的狀態管理。react

在實際開發中,常搭配React + React-redux使用。這表明了目前前端開發的一個基本理念,數據和視圖的分離git

redux應運而生,固然還有其餘的一些狀態管理庫,如Flux、Elm等,固然,咱們這裏只對redux進行解析。github

#redux建立Store

建立redux的store對象,須要調用combineReducers和createStore函數,下面解釋不包含中間件。編程

const reducer = combineReducers({
    home: homeNumber,
    number: addNumber
})
const store = createStore(reducer)
// 暫時掛載在window下,下面會使用到
window.$reduxStore = store
複製代碼

#combineReducers函數

首先調用combineReducers函數,將多個reducer函數做爲參數傳入,源碼以下:redux

// reducers便是傳入的參數對象
function combineReducers(reducers) {
    // ......省略
    return function combination(state = {}, action) {
        let hasChanged = false
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
            // finalReducerKeys 是傳入reducers對象的key值
            const key = finalReducerKeys[i]
            // finalReducers 等價於 reducers
            const reducer = finalReducers[key]
            const previousStateForKey = state[key]
            // 運行reducer函數,返回一個state
            // 核心:調用combination函數,實際就是循環調用傳入的reducer函數
            const nextStateForKey = reducer(previousStateForKey, action)

            nextState[key] = nextStateForKey
            // hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        // hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
        // 返回state對象
        return nextState
    }
}
// 源碼地址:https://github.com/reduxjs/redux/blob/master/src/combineReducers.ts#L139
複製代碼

上面的代碼其實很是簡單,combineReducers函數運行,返回一個新的combination函數。combination函數的主要做用是返回一個掛載所有state的對象當combination函數被調用時,實際就是循環調用傳入的reducer函數,返回state對象。將combination函數做爲參數傳入到createStore函數中。數組

#createStore函數

function createStore(reducer, preloadedState, enhancer) {
    // reducer --> combination函數
    let currentReducer = reducer
    // 所有的state屬性,掛載在currentState上
    let currentState = preloadedState

    // 下面的中間件會用到
    if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        // 第二個參數是一個函數,沒有第三個參數的狀況
        enhancer = preloadedState
        // 將preloadedState重置
        preloadedState = undefined
    }
    if (typeof enhancer !== 'undefined') {
        // 存在中間件時,將createStore傳入中間件函數,調用enhancer函數,return結束。
        return enhancer(createStore)(reducer, preloadedState)
    }

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

    // 初始化調用dispatch,建立初始state
    dispatch({ type: ActionTypes.INIT })

    const store = ({
        dispatch: dispatch,
        subscribe,s
        getState,
        replaceReducer,
        [$$observable]: observable
    }
    return store
}
// 源碼地址:https://github.com/reduxjs/redux/blob/master/src/createStore.ts#L60
複製代碼

reducer就是傳入的combination函數,preloadedState是初始化的state(沒有太大的做用),enhancer是中間件,沒有第三個參數enhancer的狀況下,同時第二個參數preloadedState是一個函數,preloadedState被賦值給enhancer。bash

調用dispatch函數初始化,currentReducer便是傳入combination函數,就向上文提到的,調用combination函數實際就是循環調用reducer函數。全部的state對象,被掛載在內部變量currentState上。存在中間件enhancer時,將createStore傳入中間件函數,調用enhancer函數,return結束,這個下文會繼續講到。前端框架

建立的store對象,暴露出的方法以下:

const store = ({
    // 分發 action,這是觸發 state 變化的唯一途徑。
    dispatch: dispatch as Dispatch<A>,
    // 變化監聽器
    subscribe,
    // 獲取store下的 所有state
    getState,
    // 替換 store 當前用來計算 state 的 reducer
    replaceReducer
}
return store
複製代碼

dispatch函數觸發action,調用reducer函數,修改state。subscribe函數能夠監聽變化state的變化。getState函數獲取所有state。replaceReducer函數替換用來計算state的reducer函數。

經過combineReducers函數合併reducer函數,返回一個新的函數combination(這個函數負責循環遍歷運行reducer函數,返回所有state)。將這個新函數做爲參數傳入createStore函數,函數內部經過dispatch,初始化運行傳入的combination,state生成,返回store對象

#redux中間件

最好把上面看懂以後,再看中間件部分!!下面對中間件進行分析:

redux-thunk只是redux中間件的一種,也是比較常見的中間件。redux-thunk庫容許你編寫與store交互的異步邏輯。

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

const store = createStore(
    reducer,
    applyMiddleware(
        thunkMiddleware, // 異步支持
    )
)
複製代碼

createStore函數支持三個參數,若是第二個參數preloadedState是一個函數,而沒有第三個參數enhancer的話,preloadedState會被賦值給enhancer。

下面會以redux-thunk中間件做爲例子,下面就是thunkMiddleware函數的代碼:

// 部分轉爲ES5代碼,運行middleware函數會返回一個新的函數,以下:
return ({ dispatch, getState }) => {
    // next實際就是傳入的dispatch
    return function (next) {
        return function (action) {
            // redux-thunk核心
            if (typeof action === 'function') { 
                return action(dispatch, getState, extraArgument);
            }
            return next(action);
        };
    };
}
// 源碼地址:https://github.com/reduxjs/redux-thunk/blob/master/src/index.js
複製代碼

redux-thunk庫內部源碼很是的簡單,github: redux-thunk 源碼

,容許action是一個函數,同時支持參數傳遞,不然調用方法不變。

#applyMiddleware函數

// 中間件調用
return enhancer(createStore)(reducer, preloadedState)

等價於

return applyMiddleware(
    thunkMiddleware,
)(createStore)(reducer, preloadedState)
複製代碼

redux的中間件,從applyMiddleware函數開始,它主要的目的就是爲了處理store的dispatch函數

// 支持多箇中間件傳入
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, ...args) => {
    // 建立 store
    const store = createStore(reducer, ...args)

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    // 遍歷運行中間件函數,將middlewareAPI做爲參數傳入
    // middleware對應上面的redux-thunk庫核心代碼,固然也支持多箇中間件
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 核心:將全部中間件傳入到compose中,返回一個新的dispatch
    dispatch = compose(...chain)(store.dispatch)
    // 照常返回一個store對象,dispatch已經被處理過了
    return {
      ...store,
      dispatch
    }
  }
}
// 源碼地址:https://github.com/reduxjs/redux/blob/master/src/applyMiddleware.ts#L55
複製代碼

applyMiddleware函數接收多個middlewares參數,返回一個store對象。經過createStore建立store對象,middlewareAPI對象掛載getState和dispatch,循環middlewares中間件,將middlewareAPI做爲參數傳入每一箇中間件。遍歷結束之後,拿到了一個包含全部中間件新返回函數的一個數組,將其賦值給變量chain。

// 遍歷以後chain的值,這裏只是拿redux-thunk庫做爲例子
// next 就是 dispatch
chain = [function (next) {
    return function (action) {
        if (typeof action === 'function') { // redux-thunk核心
            return action(dispatch, getState, extraArgument);
        }
        return next(action);
    };
}, ...更多中間件]
複製代碼

#compose函數

// 數組chain 保存全部中間件新返回函數
dispatch = compose(...chain)(store.dispatch)
複製代碼

compose的主要做用就是運行全部中間件函數後,返回一個通過處理的dispatch函數。

// compose函數
return chain.reduce((a, b) =>{
    return (...args)=> {
        return a(b(...args))
    }
}
複製代碼

chain是保存中間件函數的數組,具體的內部結構參見上面👆,下面來分析一下compose函數的調用邏輯。

// chain 類比爲 [fn1, fn2, fn3, fn4]
[fn1, fn2, fn3, fn4].reduce((a, b) =>{
    return (...args)=> {
        return a(b(...args))
    }
}
複製代碼

調用過程以下:

循環 a值 b值 返回的值
第一輪循環 fn1 fn2 (...args)=> fn1(fn2(...args))
第二輪循環 (...args)=> fn1(fn2(...args)) fn3 (...args)=> fn1(fn2(fn3(...args)))
第三輪循環 (...args)=> fn1(fn2(fn3(...args))) fn4 (...args)=> fn1(fn2(fn3(fn4(...args))))

通過 compose 處理過以後, 最後的返回值就是 (...args) => fn1(fn2(fn3(fn4(...args)))),這個的arg就是 store.dispatch函數。最後將返回函數賦值給dispatch,就是咱們須要的dispatch函數了。而若是隻有一箇中間件的話,就會直接返回了。

applyMiddleware函數中間件的主要目的就是修改dispatch函數,返回通過中間件處理的新的dispatch函數

#redux使用

這裏的使用是不配合react-redux+react的;

window.$reduxStore = store

store.dispatch(action);

let { aState } = store.getState()
複製代碼

上面是直接將其掛載在window對象之下,這樣能夠配合任何前端框架使用,固然這樣確定是不優雅的,後面我再會專門講一篇配合react-redux使用的;

在這裏調用store.dispatch函數,實際就是再次調用循環遍歷調用reducer函數,更新以後被保存在createStore函數的內部變量currentState上。經過store.getState函數,返回currentState變量,便可獲得全部state。

#結束:

內容有點多,須要總結一下

1.redux建立Store經過combineReducers函數合併reducer函數,返回一個新的函數combination(這個函數負責循環遍歷運行reducer函數,返回所有state)。將這個新函數做爲參數傳入createStore函數,函數內部經過dispatch,初始化運行傳入的combination,state生成,返回store對象

2.redux中間件applyMiddleware函數中間件的主要目的就是修改dispatch函數,返回通過中間件處理的新的dispatch函數

3.redux使用實際就是再次調用循環遍歷調用reducer函數,更新state

解析到這裏就結束了,redux真的很是函數化,滿滿的函數式編程的思想,很是的模塊化,具備很強的通用性,以爲很是贊👍👍

ps: 微信公衆號:Yopai,有興趣的能夠關注,每週不按期更新,分享能夠增長世界的快樂

相關文章
相關標籤/搜索