redux的reateStore,combineReducers,bindActionCreators,applyMiddleware源碼分析

最新內容,請在github閱讀。同時,All issue and star welcomed!

1.深入分析redux中的createStore方法

我們首先以一個官方demo來說,代碼如下:

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import Counter from './components/Counter'
import counter from './reducers'
const store = createStore(counter)
const rootEl = document.getElementById('root')
const render = () => ReactDOM.render(
  <Counter
    value={store.getState()}
    onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
    onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
  \/>,
  rootEl
)
render()
store.subscribe(render)

下面我們來看看createStore的作用:

const store = createStore(counter)
//下面就是counter函數
export default (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

createStore表示我們創建一個數據庫,這個數據庫計算下一個狀態的方式是通過counter來完成的。而且,如果你要計算下一個狀態,需要給我傳入當前狀態,以及你需要的特定的操作的名稱,即Action。同時,這個數據庫也提供了很多其他的方法。

    getState: 獲取當前的數據庫的狀態

    subscribe: 當你對數據庫發出一個指令,而且數據庫根據這個指令已經計算得到新的狀態
    以後需要執行的回調函數

    dispatch:發出一個Action,告訴數據庫你要幹嘛。數據庫會根據當前的狀態以及你的命令類型計算得到新的狀態。計算完成以後,我們要執行subscribe添加的所有的回調函數.

    replaceReducer:用一個新的store替換掉我們當前的store用來計算我們的新的state。如果你的app支持code splitting, 而且你想要動態加載reducer。同時如果你要爲你的Redux支持Hot Reloading也需要

    observable:

其中observable的內容如下:

 function observable() {
    var outerSubscribe = subscribe
    return {
      subscribe(observer) {
        if (typeof observer !== 'object') {
          throw new TypeError('Expected the observer to be an object.')
        }
        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }
        observeState()
        var unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },
      [$$observable]() {
        return this
      }
    }
  }

該方法的實現不是本文討論的重點,你可以查看文末的參考資料。此時我們回到上面的源代碼。

第一步:createStore構建數據庫並傳入計算新state的規則

const store = createStore(counter)

數據庫會有上面的getState,subscribe,dispatch,replaceReducer,observable等各種方法。你可以給它註冊事件,也可以從它哪裏獲取到數據。

第二步:註冊回調函數。當數據庫收到指令,並且通過它計算到新的值的時候重新render。也就是說,每次dispatch導致數據庫狀態變化後我們都會重新render

store.subscribe(render)

第三步:渲染render頁面

const render = () => ReactDOM.render(
  <Counter
    value={store.getState()}
    //獲取數據庫狀態
    onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
    //向數據庫發送指令
    onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
  \/>,
  rootEl
)
render()

2.深入分析redux中的combineReducers方法

首先貼上源碼(關鍵部分):

export default function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  //對每一個reducer進行循環,將那些導出是函數的reducer保存到finalReducers中
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  var finalReducerKeys = Object.keys(finalReducers)
  //得到我們關注的reducer所有的鍵名
  if (NODE_ENV !== 'production') {
    var unexpectedKeyCache = {}
  }
  var sanityError
  try {
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }
  //此時我們的combineReducers返回一個函數了,也就是最終結合起來的reducer
  //和普通的reducer一樣,接收state與action爲兩個參數
  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }
    if (NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    var hasChanged = false
    var nextState = {}
    //http://redux.js.org/docs/api/combineReducers.html
    //此時key爲["todos","counter"]
    //store.dispatch({
    //   type: 'ADD_TODO',
    //   text: 'Use Redux'
    // })
    // 此時表示我們會要reducers去處理,但是具體讓那個reducer處理是要我們自己選擇的~~
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      //此時如todos,counter兩個key
      var reducer = finalReducers[key]
      //得到某個key對應的具體的reducer函數。如上面這種情況key和value是一致的,爲todos和counter
      var previousStateForKey = state[key]
      //對於每一個reducer只會處理相應的key,所以我們的state肯定是如下的格式
      //{todos:[],counter:0},也就是我們 ${ActionTypes.INIT}的初始值
      var nextStateForKey = reducer(previousStateForKey, action)
      //第一次循環如果是todos,那麼我們會獲取state中的todos的值以及action傳入計算得到新的todos值
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      //更新這個key的值
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
      //這個state是否發生變化
    }
    //如果沒有一個屬性的值發生變化,那麼hasChanged就是爲false
    return hasChanged ? nextState : state
  }
}

我們真實使用的使用是如下方式的:

  import todos from './todos'
  import counter from './counter'
  export default combineReducers({
    todos,
    counter
  })

你應該知道,我們這裏導出的調用combineReducers後的結果其實就是我們組合後的reducer函數,這個函數會被傳入到createStore中,就像以前我們傳入單個reducer函數到createSStore中一樣。對於每一個reducer函數的編寫也是有具體的要求的:

function assertReducerSanity(reducers) {
  Object.keys(reducers).forEach(key => {
    var reducer = reducers[key]
    var initialState = reducer(undefined, { type: ActionTypes.INIT })
    //通過對我們的reducer執行ActionTypes.INIT,得到這個reducer的初始值
    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
        `If the state passed to the reducer is undefined, you must ` +
        `explicitly return the initial state. The initial state may ` +
        `not be undefined.`
      )
    }

    var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    //我們的每一個reducer傳入的第一個參數是state,第二個參數是Action
   //(1)不要處理@@redux這個空間下的Action或者@@redux下的${ActionTypes.INIT},他們是私有的
   //(2)對於我們未知的action我們必須返回當前的state狀態,除非這個Action是undefined.
   //   此時因爲action是undefined,所以我們返回初始值,而不管Action本身的類型
    if (typeof reducer(undefined, { type }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
        `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
        `namespace. They are considered private. Instead, you must return the ` +
        `current state for any unknown actions, unless it is undefined, ` +
        `in which case you must return the initial state, regardless of the ` +
        `action type. The initial state may not be undefined.`
      )
    }
  })
}

注意三點:通過對我們的reducer執行ActionTypes.INIT,得到這個reducer的初始值;不要處理@@redux這個空間下的Action或者@@redux下的${ActionTypes.INIT},他們是私有的;對於我們未知的action我們必須返回當前的state狀態,除非這個Action是undefined, 此時因爲action是undefined,所以我們返回初始值(不是當前值),而不管Action本身的類型。還有一點需要注意:

  for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      //此時如todos,counter兩個key
      var reducer = finalReducers[key]
      //得到某個key對應的具體的reducer函數。如上面這種情況key和value是一致的,爲todos和counter
      var previousStateForKey = state[key]
      //對於每一個reducer只會處理相應的key,所以我們的state肯定是如下的格式
      //{todos:[],counter:0},也就是我們 ${ActionTypes.INIT}的初始值
      var nextStateForKey = reducer(previousStateForKey, action)
      //第一次循環如果是todos,那麼我們會獲取state中的todos的值以及action傳入計算得到新的todos值
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      //更新這個key的值
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
      //這個state是否發生變化
    }

對於這個for循環來說,傳入一個Action的情況下,我們對將兩個reducer函數(todos與counter)都執行它。對於不處理這個Action.type的reducer,我們會直接根據reducer返回當前的值,即currentState。同時,我們也必須注意,我們返回的state肯定是如下的類型,即肯定包含todos與counter屬性,即使是獲取初始${ActionTypes.INIT}值( nextState[key] = nextStateForKey):

{
  todos:[],
  counter:0
}

3.深入分析redux中的bindActionCreators方法

我們直接給出源代碼:

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }
  var keys = Object.keys(actionCreators)
  //獲取keys
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    //得到我們的actionCreator方法
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
    //boundActionCreators["removeTodo"]=function(){}
    //boundActionCreators["addTodo"]=function(){}
    //所以,返回的內容很簡單,就是把我們自己的函數包裝一下,返回一個新的函數
    //當你調用這個新的函數的時候,新函數會直接dispatch我們的舊函數調用的值
    //同時將新函數的參數也傳遞給舊函數
  }
  return boundActionCreators
}

代碼很簡單,我們先給出調用的例子:

 export function addTodo(text) {
  return {
    type: 'ADD_TODO',
    text
  }
}
//通過bindActionCreators包裹的函數必須是action creators
export function removeTodo(id) {
  return {
    type: 'REMOVE_TODO',
    id
  }
}
let boundActionCreators = bindActionCreators(TodoActionCreators, dispatch)

所以,返回的內容很簡單,就是把我們自己的函數包裝一下,返回一個新的函數
當你調用這個新的函數的時候,新函數會直接**dispatch我們的舊函數調用後的值
同時將新函數的參數也傳遞給舊函數**。所以上面的例子得到的數據類型如下:

{
  addTodo:function(){},
  removeTodo:function(){}
  //這裏的function是對我們的函數進行了一次包裹
}

4.深入分析redux中的applyMiddleware方法

直接貼上源碼:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
 //如果中間件的個數爲1,那麼獲取第一個中間件
  if (funcs.length === 1) {
    return funcs[0]
  }
  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  //last表示最後一箇中間件,而rest標籤除了最後一箇中間件的其他中間件
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  //初始值爲h(...args),reduceRight第一個函數的第一個參數爲previousValue
}
//For example, compose(f, g, h) is identical to doing
//(...args) => f(g(h(...args))).

下面是applyMiddleware源碼:

export default function applyMiddleware(...middlewares) {
  //createStore=>reducer, preloadedState, enhancer
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    //得到一個store
    var dispatch = store.dispatch
    //獲取到store上的dispatch
    var chain = []
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)
    // (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
    //[middleware1,middleware2,middleware3]
    // middleware1(middleware2(middleware3(last(store.dispatch))))
    return {
      ...store,
      dispatch
    }
  }
}

下面是一個middleware的中間件的例子:

 function logger({ getState }) {
      return (next) => (action) => {
        console.log('will dispatch', action)
        // Call the next dispatch method in the middleware chain.
        let returnValue = next(action)
        console.log('state after dispatch', getState())
        // This will likely be the action itself, unless
        // a middleware further in chain changed it.
        return returnValue
      }
    }

從源碼中我們可以看到,中間件的執行是分爲下面三個步驟的:

第一步:調用我們middleware,傳入getState與dispatch

  var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }

第二次:再次調用我們的middleware,傳入store.dispatch

    dispatch = compose(...chain)(store.dispatch)

第三次:不管什麼時候調用,調用方式如何,只要傳入action即可。這也是我們唯一需要自己控制的一步~

  return {
      ...store,
      dispatch
    }

還有一點需要注意,我們真實調用applyMiddleware時候的方式是:

let store = createStore(
  todos,
  [ 'Use Redux' ],
  applyMiddleware(logger)
)

很顯然,我們上面的applyMiddleware第二級函數參數是createStore,那麼是什麼時候調用的呢?我們看看createStore方法(只是摘取關鍵部分演示):

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.')
    }
    //applyMiddleware(logger)此處繼續傳入createStore與reducer,preloadedState
    //同時return直接返回了。但是我們要知道,在applyMiddleWare中我們創建了自己的
    //store,即 var store = createStore(reducer, preloadedState, enhancer)
    return enhancer(createStore)(reducer, preloadedState)
  }
}

是不是明白了,在createStore的時候我們傳入了applyMiddleware(logger)。那麼後面我們在createStore裏面繼續傳入了createStore與reducer, preloadedState等函數

5.redux的數據流

參考資料:

zen-observable

proposal-observable

symbol-observable

redux-observable

ServerRendering

redux-observable.js.org

從零開始搭建React同構應用(三):配置SSR

redux官網

applyMiddleware的參數爲什麼加展開符?

源碼分析倉庫

發佈了210 篇原創文章 · 獲贊 83 · 訪問量 98萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章