React中Redux核心原理深入分析

一、Redux是什麼

眾所周知,Redux最早運用於React框架中,是一個全局狀態管理器。Redux解決瞭在開發過程中數據無限層層傳遞而引發的一系列問題,因此我們有必要來瞭解一下Redux到底是如何實現的?

二、Redux的核心思想

Redux主要分為幾個部分:dispatch、reducer、state。

我們著重看下dispatch,該方法是Redux流程的第一步,在用戶界面中通過執行dispatch,傳入相對應的action對象參數,action是一個描述類型的對象,緊接著執行reducer,最後整體返回一個store對象,我們來看下這部分的源碼:

// 主函數createStore
// 返回一個store對象
export default function createStore(reducer, preloadedState, enhancer) {
  // 增強器
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    return enhancer(createStore)(reducer, preloadedState)
  }
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false
  // 獲取最終的state
  function getState() {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }
    return currentState
  }
  // dispatch
  // 參數action
  function dispatch(action) {
      // 校驗傳入的action
    // action必須是個對象,否則拋出錯誤信息
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }
    // 檢驗action對象的必要屬性
    // type屬性是action對象必要的屬性
    // 如果傳入的action沒有type屬性,則拋出錯誤信息
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
    try {
      isDispatching = true
      // 執行傳入的reducer函數
      // 返回state,給currentState賦值
      currentState = currentReducer(currentState, action)
    } finally {
        // 一個dispatch執行完,還原狀態
      isDispatching = false
    }
    // 執行訂閱函數隊列
    // dispatch執行的同時會一並執行訂閱隊列
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    // 返回action
    return action
  }
  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  // 默認執行一次dispatch,做初始化
  dispatch({ type: ActionTypes.INIT })
  // 返回一個store對象
  return {
    dispatch,
    subscribe,
    getState,
    ...
  }
}

通過源碼我們可以基本清楚,通過執行createStore方法,最終會返回一個store對象,該對象主要暴露幾個屬性,我們主要關註比較常用的:dispatch、getState、getState,看下實際用例:參考React實戰視頻講解:進入學習

import createStore from 'redux'
// 創建一個reducer
function reducer(state={}, action) {
    switch(action.type) {
        case 'TEST':
        return {
            ...state,
            test: 'test success'
        }
    }
}
// 返回store
const store = createStore(reducer, initState={})
// 執行dispatch
store.dispatch({
    type: 'TEST'
})
const state = store.getState() // 返回 {test: 'TEST'}

三、Redux中間件原理

接下來我們來探討Redux的另一個重要組成部分—中間件。什麼是Redux的中間件?Redux中間件其實是通過重寫createStore來增強和擴展原來的dispatch方法,使其能夠在執行dispatch的同時可以同步執行其它方法,比如redux-thunk就是一個處理異步的中間件:

function createThunkMiddleware(extraArgument) {
    // 中間件規定格式
    // 閉包返回三層嵌套
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;

下載瞭中間件,那麼我們來看下如何使用中間件:

import createStore, {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->applyMiddleWare} from 'reduximport reduxThunk from 'redux-thunk'// 創建一個reducerfunction reducer(state={}, action) { switch(action.type) { case 'TEST': return { ...state, test: 'test success' } }}// 返回store// 中間件作為applyMiddleWare的參數傳入createStoreconst store = createStore(reducer, initState={},applyMiddleWare(reduxThunk))

我們會發現,中間件的使用方式是用applyMiddleWare把中間件作為參數傳入createStore中,那麼applyMiddleWare是如何實現的?在這之前我們先看下createStore方法的第三個參數是什麼,我們回看下createStore源碼:

export default function createStore(reducer, preloadedState, enhancer) {
  ...
  // 增強器
  // 第三個參數是enhancer,也就是我們傳入的applyMiddleWare
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    // 在這裡return瞭enhancer結果
    // 傳入瞭createStore,reducer,preloadedState
    // 實際上是重寫瞭createStore
    return enhancer(createStore)(reducer, preloadedState)
  }
  ...
}

看完瞭enhancer的實際作用,我們可以弄清楚applyMiddleWare的實現原理,請看源碼:

import compose from './compose'
// 傳入middlewares中間件
export default function applyMiddleware(...middlewares) {
  // 閉包嵌套返回2個方法
  return createStore => (...args) => {
      // 返回store
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }
    // 返回一個對象
    // 包含getState方法和dispatch方法
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args) // 返回一個全新的dispatch方法,不污染原來的dispatch
    }
    // 執行中間件第一層方法
    // 回顧下中間的格式:({getState, dispatch}) => next => action => next(action)
    // 這裡會比較繞
    const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 返回一個中間件的函數集合[next => action => next(action), next => action => next(action)]
    // 使用compose聚合chain函數集合
    // 返回新的dispatch
    dispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
}

這裡可能會讓人很疑惑,不大清楚的童鞋可以先看下中間件的規范寫法,這裡還有一個重要的函數compose,我們來看下compose怎麼處理chain函數集合的,請看源碼:

/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */
// 傳入聚合函數集合
// 集合為:[next => action => next(action), next => action => next(action)]
// 返回一個新的函數: (arg) => arg  
export default function compose(...funcs) {
  // 判斷如果沒有則返回一個新函數
  // 可以聯想一下dispatch的定義
  // function dispatch(action) {
      ...
      return action
  }
  if (funcs.length === 0) {
    return arg => arg
  }
  // 判斷如果隻有一個中間件,則直接返回第一個
  if (funcs.length === 1) {
    return funcs[0]
  }
  // 這裡用瞭reduce函數
  // 把後一個的中間件的結果當成參數傳遞給下一個中間件
  // 函數列表的每個函數執行後返回的還是一個函數:action => next(action)
  // 這個函數就是新的dispatch
  // 最後返回函數:(...args) => action => args(action)
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose的源碼及其簡潔,但是很精髓,幾乎是整個中間件最出彩的地方。通過reduce把每個中間件都執行一遍,並且是通過管道式的傳輸,把每個中間件的返回結果當成參數傳遞給下一個中間件,實現瞭剝洋蔥式的中間件模式。這裡比較難理解,新手可以先寫幾個簡單的中間件,然後再去慢慢理解為什麼要這麼處理,理解後就會知道這段代碼有多簡潔瞭。

四、手寫一個Redux

源碼解析完瞭,我們來簡單實現一個redux。

createStore

// 判斷值是否是對象類型
function isPlainObject(obj) {
    if(!obj) {
        reutrn false
    }
    return Object.prototype.toString.call(obj) === '[object, Object]'
}
export default createStore(reducer, enhancer) {
    // 先判斷有沒有傳入中間件
    // 有則之間返回
    if(typeof enhancer !== 'undefined') {
        // 必需是個函數
        // 因為需要傳參
        if(typeof enhancer !== 'function') {
            return
        }
        return enhancer(createStore)(reducer)
    }
    let state = {} // 初始化state
    let listeners = [] // 發佈訂閱函數隊列
    // 定義getState 函數
    function getState() {
        // 直接返回state
        return state
    }
    // 定義dispatch 函數
    function dispatch(action) {
        try{
            // 執行reducer, 返回state
            state = reducer(state, action)
        }catch(e) {
            console.log('dispatch error: 'e)
        } 
        // 訂閱
        listeners.forEach(listener => listener())
        // 返回action
        return action
    }
    // 定義subscribe 函數
    function subscribe(listener) {
        if(!listener) {
            return
        }
        // 必需是回掉函數
        // 因為需要在dispatch裡執行
        if(typeof listener !== 'function') {
            return
        }
        Listeners.push(listener)
    }
    // 返回對象:包含getState, dispatch, subscribe 三個方法
    return {
        getState,
        dispatch,
        subscribe
    }
}

compose

    function compose(...funs) {
        if(!funs) {
            return arg => arg
        }
        if(funs.length === 1) {
            return funs[0]
        }
        // 遍歷傳入函數,返回一個新函數
        return funs.reduce((a,b) => (...args) => a(b(...args)))
    }

applyMiddleWare

import compose from './compose'
function applyMiddleWare(...middlewares) {
    return createStore => reducer => {
        // 先返回一個store
        const store = createStore(reducer)
        // 創建middleApi
        const middleApi = {
            getState: store.getState,
            dispatch: (...args) => store.dispatch(...args) // 返回一個新的dispatch
        }
        // 註入middleApi
        // 並返回函數集合
        const chain = middlewares.map(middleWare => middleWare(middleApi))
        // 通過compose函數,執行所有中間件,並返回一個新的dispatch
        const dispatch = compose(...chain)(store.dispatch)
        // 返回store對象
        return {
            getState: store.getState,
            dispatch
        }
    }
}

logger中間件

    function logger({getState, dispatch}) {
        return function(next) {
            return function(action) {
                console.log('prev')
                next(action)
                console.log('done')
            }
        }
    }

測試

    import createStore from './myCreateStore'
    import applyMiddleWare from './myApplyMiddleWare'
    import logger from './logger'
    // 創建reducer
    function reducer(state={}, action) {
        switch(action.type) {
            case 'TEST':
            return {
                ...state,
                test: 'test success'
            }
        }
    }
    // 引入中間件
    const middleware = applyMiddleWare(logger)
    const store = createStore(reducer, middleware) // 返回{getState, dispatch}

總結

至此一個完整的redux我們就已經分析完瞭,個人認為中間件的compose這裡是比較不好理解的點,但是隻要明白中間件主要要解決的是增強dispatch函數,就可以順著這個思路去理解。接著再試著寫幾個中間件,進一步理解為什麼中間件的格式需要返回嵌套的三層函數,明白瞭這兩個點,redux的原理也就基本能夠明白瞭,有問題歡迎在評論中指出。

到此這篇關於React中Redux核心原理深入分析的文章就介紹到這瞭,更多相關React Redux內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: