redux源碼結合實踐深刻解析

背景

redux做爲前端狀體管理中最亮眼的那個仔,很是有必要弄清楚他的原理。本文將從源碼結合實踐一塊兒來從新認識redux。純乾貨分享!!!javascript

redux相對來說是相對比較複雜的狀態管理工具。實現一個全局狀態管理工具,經過一個全局變量和一些方法,便可實現的東西,那麼爲何redux須要提出action,store,dispatch,reducer等一系列概念?提出這些概念的做用或者說動機是什麼?但願讀者能從這篇文章中深刻理解這些概念存在的價值和意義。前端

export const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
);
複製代碼

咱們常常看到這段代碼,本文將從以createStore做爲入口順藤摸瓜帶你認識整個框架。下面源碼是v3.7.2版本的代碼。java

createStore

首先來看createStore函數源碼, 爲了方便理解和閱讀省略了不少無關的代碼,你們在閱讀的時候能夠摺疊起來看。react

export default function createStore(reducer, preloadedState, enhancer) {
   // 若是隻有兩個參數,而且第二個參數是函數的話,將會傳遞給enhancer
   if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
    
    // 省略一堆判斷邏輯
    return enhancer(createStore)(reducer, preloadedState)
  }
   
   // 一堆方法定義
   dispatch({ type: ActionTypes.INIT });
   return {
    dispatch,  // 重點講述
    subscribe, // 重點講述
    getState, // 返回state的方法
    replaceReducer, // 高級用法,目的在於分包時,動態更換reducer
    [$$observable]: observable
  }
}
複製代碼
  • 從代碼中能夠看到store的返回值是一個對象,具備多種方法
  • enhancer的做用是功能擴展,返回值是一個store, enhancer函數的寫法舉例
function myEnhancer(createStore){
    return (reducer, preloadedState, enhancer) => {
       //建立store以前, do someSting
        const store = createStore(reducer, preloadedState, enhancer)
        //store以後, do something
        return store;
    }
}
複製代碼
  • store建立以後,就會dispatch一個默認的初始action,來作初始化。這步操做能夠類比與函數自執行,目的是爲了讓每一個reducer返回他們默認的state構成初始全局state。
  • 全局state其實就是一個普通對象函數,其餘操做都是來輔助管理該state

dispatch

dispatch是咱們的重頭戲,後面仍是介紹他,咱們先看下,當咱們dispatch({ type: 'INCREACE', payload: 1})會發生些什麼呢。redux

function dispatch(action) {
    // 各類檢查acton類型
    try {
      isDispatching = true
      // currentState是原來的state
      // currentReducer就是一開始createStore時傳入的reducer
      currentState = currentReducer(currentState, action)
      // reducer以後返回的是更新後的新的state
    } finally {
      isDispatching = false
    }

    // 更新監聽者函數
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }
複製代碼
  • dispatch觸發一個action,執行reducer,而後更新監聽者,最後返回action自己。這裏爲何要返回action呢?答案是爲了中間件的鏈式複合,在中間件部分會詳細解釋。
  • action的類型檢查中要求,action必須是一個普通對象,必須有type屬性
  • reducer是一個函數,接收兩個參數state和action,並返回新的state,初始化時,state多是undefined,所以經過觸發默認action,來返回reducer的初始state。reducer常見格式以下:
function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: true
          })
        }
        return todo
      })
    default:
      return state
  }
}
複製代碼
  • reducer執行完成後,更新state,而後觸listeners函數,沒有任何參數,其中一個的應用是react-redux中的高階組件connect更新機制,後面會深刻解析react-redux,請持續關注哦!

subscribe

subscribe是一個簡單的監聽者模式,該函數主要是收集監聽者。源碼很簡單以下bash

function subscribe(listener) {
    // 檢查listener類型
    let isSubscribed = true
    
    ensureCanMutateNextListeners() 
    // 該函數會複製一份currentListeners
    // 保障更新期間其餘listener不受影響
    nextListeners.push(listener)
    
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }
      // 省略部分錯誤檢查
      isSubscribed = false
    
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
      // 下次運行時 currentListeners會從新從nextListeners中取,能夠看dispatch的代碼
      // 做者這樣作的目的主要是爲了防止dispatch執行期間發生subscribe或者unsubscribe引起異常錯誤
    }
}
複製代碼

到這裏,整個redux的核心功能就介紹的差很少了,可是redux的威力並無體現出來,接下來咱們將介紹redux的擴展功能中間件。閉包

applyMiddleware

該函數是一個enhancer函數,由redux實現提供, 用來嵌入中間件,也是咱們常用的一個工具函數。架構

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    // 特別注意這個dispatch是使用let賦值的
    // 這個預約義是爲了防止用戶提早使用,此時沒法觸發其餘中間件
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
      // 這個dispatch方法不能在next函數前使用
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
複製代碼
  • 輸入參數是一些列中間件,返回值是一個store對象(能夠對照createStore的代碼),dispatch函數進行了封裝。
  • compose函數實現了一個簡單的洋蔥模型,上一個函數的輸入做爲下一個函數的輸出,後面會詳細介紹。
  • 從代碼中發現,每一箇中間件會輸入getState和dispatch對象,返回值須要知足compose函數要求。舉例以下,下面例子中能夠記錄每一個action到更新state所花費的時間。
function loggerMiddleware({getState, dispatch}){ // 這部分對應的是middleware(middlewareAPI)
    // 這塊區域不能使用dispatch函數,不然會拋出錯誤!!
    return next => action => {
        console.time(action.type);
        const result = next(action);
        // result 對象是一個action類型的對象,若是中間件未修改過該值,則全等,通常來說,action不該該被修改
        console.timeEnd(action.type);
        return result;  // 將會傳入下一個中間中
    }
}
複製代碼

在書寫中間件的時候,咱們發現內部閉包了多個函數,若是部分函數採用async等方式的話,就能夠實現異步操做,解決反作用的問題,redux-thunk正是借用這種方式實現,感興趣的同窗能夠學習下,代碼只有14行,這裏就不展開討論了。app

  • next函數是上一個中間件的返回值,是上一個中間件封裝後返回的dispatch,next(action)的做用至關於dispatch(action),他會觸發後續的中間件,所以next命名比較形象

compose

compose是一個函數構造器,返回一個新的函數。相似數學中的函數f(x),g(x),h(x)複合爲f(g(h(x)))。上一個函數的輸出做爲下一個函數的輸入。框架

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼
  • 出於js單個返回值的限制,每一個函數的參數只能有一個
  • 若是參數x是一個值的時候,compose函數執行後也會獲得一個值, 即 compose(a,b,c)(x)也會返回一個值。舉例(九折後滿500再減50):
function couponA(next) {
    if(next >= 500){
        return next - 50;
    }
    return x;
}
function couponB(next){
    return next * 0.9;
}
const discount = compose(couponA, couponB);
discount(1000); // 850
複製代碼

當參數是一個值的時候,沒法實現迴旋鏢的形式。上述例子實際上是一個簡單的職責鏈模式,感興趣的能夠深刻挖掘,在電商打折規則中特別實用

  • 若是參數是一個函數的時候,每一箇中間件也返回一個函數,如applyMiddleware中的dispatch。因爲dispatch是一個函數,能夠利用函數調用時執行的特色,實現迴旋鏢型的中間件,如上述loggerMiddleware,能夠記錄dispatch所花費的時間。
  • compose函數中處理函數是從右向左執行,即最後一個函數先執行。

combineReducers

這是一個工具函數,能夠將多個reducer聚合起來,返回值是一個reducer(這是一個函數)

// reducers是一個
export default function combineReducers(reducers) {
    // 省略對reducers作了一堆檢查
    // 下面這句是爲了好理解,我杜撰的,非真實源碼
    const finalReducers = {...reducers}
    const finalReducerKeys = Object.keys(finalReducers);
    function combination(state = {}, action) {
        // 此處省略了一些檢查
        let hasChanged = false
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
          const key = finalReducerKeys[i]
          const reducer = finalReducers[key]
          const previousStateForKey = state[key]
          // 這裏以key劃分命名空間,previousStateForKey爲指定key下的state
          const nextStateForKey = reducer(previousStateForKey, action)
          if (typeof nextStateForKey === 'undefined') {
            // 每一個reducer都應該有返回值 
            const errorMessage = getUndefinedStateErrorMessage(key, action)
            throw new Error(errorMessage)
          }
          nextState[key] = nextStateForKey
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state
    } 
}
複製代碼
  • 使用combineReducers後,對應的state也具備與reducers對象具備相同的結構。

bindActionCreators

該函數是redux提供的一個工具函數,首先要弄清楚action和actionCreator的關係。action是一個普通對象,actionCreator是一個構造action對象的函數

bindActionCreator的目的是將actionCreator與dispatch結合構造出一個可以直接觸發一系列變化的Action方法 bindActionCreators就是將多個actionCreator轉化爲Action方法

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 省略一系列檢查
  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
複製代碼

在實踐中,結合reat-redux的connect的第二個參數mapDispatchToProps爲例,展現actionCreators轉化爲能夠直接運行的方法。

const actionCreators = {
    increase: (payload) => ({ type: 'INCREASE', payload }),
    decrease: (payload) => ({ type: 'DECREASE', payload })
}

@connect(
    state => state,
    dispatch => ({
        actions: boundActionCreators(actionCreators, dispatch)
    })
)
class Counter {
    render(){
        <div>
            <button onClick={() => this.props.actions.increase(1)}>increase</button>
            <button onClick={() => this.props.actions.decrease(1)}>decrease</button>
        </div>
    }
}
複製代碼

總結

  1. redux管理狀態是經過一個currentState對象來存儲全局狀態,可是將修改狀態拆分爲了dipatch(action)和reducer兩部分,大大提升工具庫的靈活性和想象空間。
  2. 理解並學會redux中間件的寫法,更加深刻了解compose函數
  3. redux相對比較複雜,但在其基礎上衍生了大量的第三方工具庫,足見其生命力,在實踐中體會做者架構的深意。
  4. 爲了便於理解,刪除了不少類型判斷,這些類型判斷可以幫助開發者更好的調試代碼,一樣很是重要,你們在本身研究源碼時,不要忽視這些細節。
  5. 文章中包含了本身大量的理解,描述和理解有不妥之處,請批評指正!!!
相關文章
相關標籤/搜索