redux源碼閱讀

Redux 是可預(yù)測的狀態(tài)管理框架,它很好的解決多交互,多數(shù)據(jù)源的訴求。

三大原則:

單一數(shù)據(jù)源:
整個應(yīng)用的state被存儲在一顆object tree中,并且這顆 object tree存在于唯一的store里,store就是存儲數(shù)據(jù)的容器,容器里面會維護(hù)整個應(yīng)用的state。store提供四個API:dispatch subscribe getState replaceReducer。 數(shù)據(jù)源是唯一的,所以獲取數(shù)據(jù)的唯一方式就是store.getState()。
state只讀:
根據(jù)state只讀原則,state的變更要通過store的dispatch(action)方法,action就是變更數(shù)據(jù)的載體,action = {type: '', payload},type是變更數(shù)據(jù)的唯一標(biāo)志, payload是需要變更的數(shù)據(jù)。
使用純函數(shù)變更state
reducer是純函數(shù),接受兩個參數(shù)即 state和action, 根據(jù)action的type屬性執(zhí)行對應(yīng)的代碼,更改相應(yīng)的數(shù)據(jù),然后返回新的state。

完整的數(shù)據(jù)流過程:

view層觸發(fā)actionCreator, actionCreator通過store.dispatch(action) ,reducer執(zhí)行對應(yīng)的代碼,返回新的state,更新頁面。


image.png

源碼結(jié)構(gòu):

src
├── utils ---------------------------------------- 工具函數(shù)
├── applyMiddleware.js --------------------------- 加載 middleware
├── bindActionCreators.js ------------------------ 生成將 action creator 包裹在 dispatch 里的函數(shù)
├── combineReducers.js --------------------------- 合并 reducer 函數(shù)
├── compose.js ----------------------------------- 組合函數(shù)
├── createStore.js ------------------------------- 創(chuàng)建一個 Redux store 來儲存應(yīng)用中所有的 state
├── index.js ------------------------------------- 入口 js

index.js

index.js文件是整個代碼的入口,該文件暴露了一些接口供開發(fā)者使用: createStore, combineReducers, bindActionCreators, applyMiddleware, compose,接下來逐個分析這些接口。

createStore

createStore是redux最重要的API,它創(chuàng)建一個store,保存應(yīng)用中的state。
createStore函數(shù)接受三個參數(shù):reducer, preloadedState, enhancer,
返回值:dispatch、subscribe、getState、replaceReducer 和 [$$observable],這就是我們開發(fā)中主要使用的幾個接口。

參數(shù)

reducer: 函數(shù),返回下一個狀態(tài),接收state和action作為參數(shù)
preloadedState: state初始值,可以忽略不傳
enhancer:store的增強(qiáng)器,一般是第三方中間件,返回一個增強(qiáng)后的store creator,這個函數(shù)通常由applyMiddleware函數(shù)來生成。

返回值

dispatch

function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }
    // 判斷 action 是否有 type{必須} 屬性
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }
    // 如果正在 dispatch 則拋出錯誤
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
    // 對拋出 error 的兼容,但是無論如何都會繼續(xù)執(zhí)行 isDispatching = false 的操作
    try {
      isDispatching = true
      // 使用 currentReducer 來操作傳入 當(dāng)前狀態(tài)和 action,返回處理后的狀態(tài)
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

dispatch函數(shù)是觸發(fā)動作的函數(shù),接收action行為作為參數(shù),action必須是一個對象,且該對象必須有type參數(shù),如果action滿足條件,則調(diào)用currentReducer(其實就是createStore的參數(shù) reducer的引用),currentReducer會根據(jù)action來改變對應(yīng)的值,生成新的store,然后再遍歷nextListeners列表,調(diào)用每一個監(jiān)聽函數(shù)。

getState

// 讀取由 store 管理的狀態(tài)樹
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
}

這個函數(shù)可以獲取當(dāng)前的狀態(tài),createStore 中的 currentState 儲存當(dāng)前的狀態(tài)樹,這是一個閉包,這個參數(shù)會持久存在,并且所有的操作狀態(tài)都是改變這個引用,getState 函數(shù)返回當(dāng)前的 currentState。

subscribe

function subscribe(listener) {
  // 判斷傳入的參數(shù)是否為函數(shù)
  if (typeof listener !== 'function') {
    throw new Error('Expected the listener to be a function.')
  }

  if (isDispatching) {
    throw new Error(
      'You may not call store.subscribe() while the reducer is executing. ' +
        'If you would like to be notified after the store has been updated, subscribe from a ' +
        'component and invoke store.getState() in the callback to access the latest state. ' +
        'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
    )
  }

  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }

    if (isDispatching) {
      throw new Error(
        'You may not unsubscribe from a store listener while the reducer is executing. ' +
          'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
      )
    }

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}

除去一些邊界條件的判斷,subscribe函數(shù)最主要的是給store狀態(tài)添加監(jiān)聽函數(shù),該函數(shù)接收一個函數(shù)作為參數(shù),會往nextListeners監(jiān)聽列表加入這個函數(shù),然后會返回一個unsubscribe函數(shù),用于解綁,如果解綁,就從nextListeners列表中去掉該函數(shù),一旦調(diào)用dispatch改變store,監(jiān)聽函數(shù)就會全部執(zhí)行。

combineReducers

reducer是管理state的一個模塊,在應(yīng)用初始化的時候,它返回initialState,當(dāng)用戶調(diào)用action時,它會根據(jù)action的type屬性,進(jìn)行相應(yīng)的更新,reducer是純函數(shù),不會更改傳入的state,會返回新的state。
當(dāng)所有的reducer邏輯都寫在同一個reducer函數(shù)里面會非常的龐大,所以我們會將reducer進(jìn)行適當(dāng)?shù)牟鸱?,但是最終傳入createStore里面的是唯一的reducer函數(shù),所以我們傳入createStore前要進(jìn)行合并,就需要combineReducers方法。

參數(shù)
reducers (Object): 一個對象,它的值(value)對應(yīng)不同的 reducer 函數(shù),這些 reducer 函數(shù)后面會被合并成一個。

返回值
(Function): 它是真正 createStore 函數(shù)的 reducer,接受一個初始化狀態(tài)和一個 action 參數(shù);每次調(diào)用的時候會去遍歷 finalReducer(有效的 reducer 列表),然后調(diào)用列表中每個 reducer,最終構(gòu)造一個與 reducers 對象結(jié)構(gòu)相同的 state 對象。

combineReducers用法

rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// This would produce the following state object
{
  potato: {
    // ... potatoes, and other state managed by the potatoReducer ...
  },
  tomato: {
    // ... tomatoes, and other state managed by the tomatoReducer, maybe some nice sauce? ...
  }
}

源碼

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

   /* if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }*/

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  /*let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }*/

  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    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]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

combineReducer函數(shù)返回一個combination 函數(shù),該函數(shù)接收state和action作為參數(shù),每次調(diào)用該函數(shù)時:

1、 for (let i = 0; i < finalReducerKeys.length; i++) { ... }:遍歷 finalReducer(有效的 reducer 列表);
2、 var previousStateForKey = state[key]:當(dāng)前遍歷項的之前狀態(tài),看到這里就應(yīng)該明白傳入的 reducers 組合為什么 key 要和 store 里面的 state 的 key 相對應(yīng)了;
3、 var nextStateForKey = reducer(previousStateForKey, action):當(dāng)前遍歷項的下一個狀態(tài);
4、 nextState[key] = nextStateForKey:將 當(dāng)前遍歷項的下一個狀態(tài)添加到 nextState;
5、 hasChanged = hasChanged || nextStateForKey !== previousStateForKey:判斷狀態(tài)是否改變;
6、 return hasChanged ? nextState : state:如果沒有改變就返回原有狀態(tài),如果改變了就返回新生成的狀態(tài)對象。

applyMiddleware

bindActionCreators

  1. actionCreator創(chuàng)建動作
    在分析之前先明確ActionCreator是什么?ActionCreator是動作創(chuàng)造者或者說是動作工廠,如果我們想根據(jù)不同的參數(shù)來生成不同值的計數(shù)器,例子如下:
  const counterActionCreator = (step) => {
    return {
      type: 'increment',
      step:  step || 1,
    }
  }

2.bindActionCreator
從上述例子出發(fā),如果我們想生成不同的計數(shù)器,并分發(fā)他們,則需要按照以下寫法:

const action1 = counterActionCreator();
dispatch(action1);

const action2 = counterActionCreator(2);
dispatch(action2);

const action3 = counterActionCreator(3);
dispatch(action3);

從上面的寫法來看,每次都需要去調(diào)用actionCreator,再調(diào)用dispatch分發(fā)action,會產(chǎn)生很多繁雜重復(fù)的代碼,所以我們可以采用 bindActionCreators.js 文件里面的bindActionCreator 方法來優(yōu)化代碼,

bindActionCreator 源碼:
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

bindActionCreator會返回一個函數(shù),函數(shù)中會調(diào)用actionCreator生成action,并將action作為dispatch的參數(shù)分發(fā)掉,所以我們將action的生成動作和調(diào)用動作封裝到了一個函數(shù)里面,我們直接調(diào)用bindActionCreator返回的函數(shù)就行,不用再每次去調(diào)用actionCreator,再調(diào)用dispatch分發(fā)action,例子如下:

const increment = bindActionCreator(counterActionCreator, dispatch);

increment();
increment(2);
increment(3);
  1. bindActionCreators
    接下來看bindActionCreators函數(shù),其實是對bindActionCreator的增強(qiáng),去掉一些判斷條件,源碼部分具體如下:
function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') { // #1
    return bindActionCreator(actionCreators, dispatch) // #2
  }

  ....

  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') { // #3
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

當(dāng)actionCreators是一個函數(shù)時,直接調(diào)用bindActionCreator將整個過程封裝返回即可
當(dāng)actionCreators是一個有多個函數(shù)方法組成的對象時,遍歷該對象,如果鍵對應(yīng)的值是函數(shù),則調(diào)用bindActionCreator將整個過程封裝,并將封裝結(jié)果賦值給該鍵,最后返回一個對象,對象里面的值都被bindActionCreator封裝過。
actionCreators為對象時的例子如下:

const actionCreators = {
  increment: function(step) {
    return {
      type: 'INCREMENT',
      step: step || 1
    }
  },

  decrement: function(step) {
    return {
      type: 'DECREMENT',
      step: - (step || 1)
    }
  }
}

const newActionCreators = bindActionCreators(MyActionCreators, dispatch)

newActionCreators.increment();
newActionCreators.increment(1);

newActionCreators.decrement();
newActionCreators.decrement(1);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容