重讀redux源碼(一)

前言

對於react技術棧的前端同窗來講,redux應該是相對熟悉的。其代碼之精簡和設計之巧妙,一直爲你們所推崇。此外redux的註釋簡直完美,閱讀起來比較省事。本來也是強行讀了通源碼,如今也忘得差很少了。由於最近打算對redux進行些操做,因此又開始重讀了redux,收益匪淺。前端

關於redux的基本概念,這裏就再也不詳細描述了。能夠參考Redux 中文文檔react

閱讀源碼感覺

有不少大牛已經提供了不少閱讀經驗。
我的感受一開始就強行讀源碼是不可取的,就像我當初讀的第一遍redux,只能說食之無味,如今全忘了。redux

應該是對其基礎用法比較熟練以後,有問題或者有興趣時再讀比較好,結合文檔或者實例,完整的流程走一走。數組

此外直接源碼倉庫clone下來,本地跑一跑,實在看不懂的斷點跟進去。app

對於不理解的地方,多是某些方法不太熟悉,這時候多去找找其具體用法和目的框架

實在不明白的能夠結合網上已有的源碼實例,和別人的思路對比一下,看本身哪裏理解有誤差。函數

一句話,但願讀過以後對本身有啓發,更深刻的理解和學習,而非只是提及來讀過而已。學習

redux 提供了以下方法:ui

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}
複製代碼

下面的文章就是按照Redux 中文文檔例子的順序,來分別看下各方法的實現。spa

action和actionCreater

定義和概念

action 本質上是 JavaScript 普通對象 在 Redux 中的 actionCreater就是生成 action 的方法

//addTodo 就是actionCreater
function addTodo(text) {
  //return的對象即爲action
  return {
    type: ADD_TODO,
    text
  }
}
複製代碼

在 傳統的 Flux 實現中,當調用 action 建立函數時
通常會觸發一個 dispatch
Redux 中只需把 action 建立函數的結果傳給 dispatch() 方法便可發起一次 dispatch 過程。

dispatch(addTodo(text))
//或者 
const boundAddTodo = text => dispatch(addTodo(text))
複製代碼

固然實際使用的時候,通常狀況下(這裏指的是簡單的同步actionCreater)咱們不須要每次都手動dispatch,
react-redux 提供的 connect() 會幫咱們來作這個事情。

裏面經過bindActionCreators() 能夠自動把多個 action 建立函數 綁定到 dispatch() 方法上。

這裏先不涉及connect,咱們一塊兒看看bindActionCreators如何實現的。

在看以前,咱們能夠大膽的猜一下,若是是咱們要提供一個warper,將兩個方法綁定在一塊兒會怎麼作:

function a (){
   /*.....*/  
};
function b(f){
  /*.....*/ 
  return f()
}
複製代碼

b裏面調用a(先不考慮其餘),經過一個c來綁定一下

function c(){
    return ()=> b(a)
}
複製代碼

應該就是這麼個樣子,那麼看一下具體實現

bindActionCreators()

先看源碼:

// 綁定單個actionCreator
function bindActionCreator(actionCreator, dispatch) {
  //將方法dispatch中,避免了action建立手動調用。
  return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
   // function 說明是單個的actionCreator 直接調用bindActionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
   // 校驗,不然拋錯
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(`錯誤提示`)
  }
  //獲取keys數組,以便遍歷
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    //依次進行校驗綁定操做
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  //返回
  return boundActionCreators
}
複製代碼

該方法分爲兩部分

首先是 bindActionCreator

對單個ActionCreator方法封裝

function bindActionCreator(actionCreator, dispatch) {
  //將方法dispatch中,避免了action建立手動調用。
  return (...args) => dispatch(actionCreator(...args))
  } 
複製代碼

bindActionCreators的actionCreators指望是個對象,即actionCreator,
能夠想到下面確定是對該對象進行屬性遍歷,依次調用bindActionCreator

下面bindActionCreators的動做就是處理該對象

  • typeof actionCreators === 'function'
    單獨的方法,直接調用 bindActionCreator結束
  • 若是不是對象或者爲null,那麼拋錯
  • 對於對象,根據key進行遍歷,獲取包裝以後的 boundActionCreators 而後返回
// function 說明是單個的actionCreator 直接調用bindActionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
   // 校驗,不然拋錯
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(`錯誤提示`)
  }
  //獲取keys數組,以便遍歷
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    //依次進行校驗綁定操做
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }  
複製代碼

這樣咱們得到了綁定以後的 actionCreators,無需手動調用dispatch(同步的簡單狀況下)

reducer

action 只是描述了有事發生及提供源數據,具體如何作就須要reducer來處理(詳細介紹就略過了)。
在 Redux 應用中,全部的 state 都被保存在一個單一對象中

當reducer處理多個atcion時,顯得比較冗長,須要拆分,以下這樣:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if (index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}  
複製代碼

須要拆分的時候,每一個reducer只處理相關部分的state相比於所有state應該更好, 例如:

//reducer1 中 
 function reducer1(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return state.reducer1.a 
      //相比較state只是state.reducer1,顯然好一點
      return state.a
 }
複製代碼

每一個 reducer 只負責管理全局 state 中它負責的一部分。
每一個 reducer 的 state 參數都不一樣,分別對應它管理的那部分 state 數據

這樣須要在主函數裏,分別對子reducer的入參進行管理,能夠以下面這樣:

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}
複製代碼

固然redux提供了combineReducers()方法

import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})
複製代碼

那麼咱們來看下combineReducers是如何來實現的

combineReducers

仍是把完整的代碼放上來

export default function combineReducers(reducers) {
  // 獲取reducer的key 不做處理的話是子reducer的方法名
  var reducerKeys = Object.keys(reducers)

  var finalReducers = {}
  // 遍歷 構造finalReducers即總的reducer
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  var finalReducerKeys = Object.keys(finalReducers)

  var sanityError
  try {
    // 規範校驗
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }
    // 警報信息 
    if (process.env.NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    /** * 當有action改變時, * 遍歷finalReducers,執行reducer並賦值給nextState, * 經過對應key的state是否改變決定返回當前或者nextState * */
    // state改變與否的flag 
    var hasChanged = false
    var nextState = {}
    // 依次處理
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      // 獲取對應key的state屬性
      var previousStateForKey = state[key]
      // 目的之一,只處理對應key數據
      var nextStateForKey = reducer(previousStateForKey, action)
      // 不能返回undefined,不然拋錯
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 新狀態賦給 nextState對象
      nextState[key] = nextStateForKey
      // 是否改變處理 
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 視狀況返回state
    return hasChanged ? nextState : state
  }
}
複製代碼

入參

首先看一下入參:reducers

  • 即須要合併處理的子reducer對象集合。
  • 能夠經過import * as reducers來獲取
  • tips:
    reducer應該對default狀況也進行處理, 當state是undefined或者未定義的action時,也不能返回undefined。 返回的是一個總reducer,能夠調用每一個傳入方法,而且分別傳入相應的state屬性。

遍歷reducers

既然是個對象集合,確定要遍歷對象,因此前幾步就是這麼個操做。

// 獲取reducer key 目的在於每一個子方法處理對應key的state
  var reducerKeys = Object.keys(reducers)

  var finalReducers = {}
  // 遍歷 構造finalReducers即總的reducer
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
//獲取finalReducers 供下面遍歷調用 
var finalReducerKeys = Object.keys(finalReducers)
複製代碼

而後是規範校驗,做爲一個框架這是必須的,能夠略過

combination

返回一個function

  • 當action被dispatch進來時,該方法主要是分發不一樣state到對應reducer處理,並返回最新state

  • 先是標識變量:

// state改變與否的flag 
    var hasChanged = false
    var nextState = {}  
複製代碼
  • 進行遍歷finalReducers 保存原來的previousStateForKey

  • 而後分發對應屬性給相應reducer進行處理獲取nextStateForKey

    先對nextStateForKey 作個校驗,由於reducer要求作兼容的,因此不容許undefined的出現,出現就拋錯。

    正常的話就nextStateForKey把賦給nextState對應的key

  • 先後兩個state作個比較看是否相等,相等的話hasChanged置爲true 遍歷結束以後就得到了一個新的state即nextState

for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      // 獲取對應key的state屬性
      var previousStateForKey = state[key]
      // 目的之一,只處理對應key數據
      var nextStateForKey = reducer(previousStateForKey, action)
      // 不能返回undefined,不然拋錯
      if (typeof nextStateForKey === 'undefined') {
         //.....
      }
      // 新狀態賦給 nextState對象
      nextState[key] = nextStateForKey
      // 是否改變處理 
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
複製代碼

根據hasChanged來決定返回新舊state。

// 視狀況返回state
    return hasChanged ? nextState : state
複製代碼

到這裏combineReducers就結束了。

結束語

此次先分享一半,仍是有點多的,剩下的下次再記錄一下。拋磚引玉,提高本身,共同窗習吧。

參考文章

Redux 中文文檔

相關文章
相關標籤/搜索