Redux 源碼解析

概述

Redux 是 JavaScript 狀態(tài)容器,提供可預測化的狀態(tài)管理方案。其三大原則為:

  • 單一數(shù)據(jù)源 => 整個應用的 state 被儲存在一顆 object tree 中,并且這個 object tree只存在于唯一一個 store 中
  • state 是只讀的 => 唯一改變 state 的方法就是觸發(fā) action,action 是一個用于描述已發(fā)生事件的普通對象
  • 使用純函數(shù)來執(zhí)行修改 => 為了描述 action 如何改變 state tree,需要編寫 reducers

純函數(shù): 即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用

Redux 的核心:

  • store => store 是由 Redux 提供的 createStore 生成的,通過 createStore 方法創(chuàng)建的 store 是一個對象
  • dispatch => 調度 state 的更新
  • reducer => 處理 state 的更新。reducer 就是一個純函數(shù),接收舊的 state 和 action,返回新的 state。指定了應用狀態(tài)的變化如何響應 actions 并發(fā)送到 store 的。
  • action => action 是把數(shù)據(jù)從應用傳到 store 的有效載荷,它是 store 數(shù)據(jù)的唯一來源。action 是一個對象,其中包括 typepayload 屬性。描述變化
  • Provider => 全局注冊,在 <Provider></Provider> 內的組件都可拿到 state
  • connect => 連接組件和 store

自我實現(xiàn)

代碼,這部分主要參照了這個系列的相關文章。相關的邏輯可以參照 commit message

官方文檔

  1. 變化 vs 異步
  2. React 把處理 state 中數(shù)據(jù)的問題留給了開發(fā)者,Redux -> 處理 state 中的數(shù)據(jù)
  3. Redux 試圖讓 state 的變化變得可預測
  4. reducer 合成 -> 每個 reducer 只負責管理全局 state 中它負責的一部分。每個 reducer 的 state 參數(shù)都不同,分別對應它管理的那部分 state 數(shù)據(jù)。開發(fā)一個函數(shù)作為主 reducer,它調用多個子 reducer 分別處理 state 中的一部分數(shù)據(jù),然后再把這些數(shù)據(jù)合成一個大的單一對象。
  5. combineReducers API
  6. store 職責:
    • 維持應用的 state
    • 提供 getState() 方法獲取 state
    • 提供 dispatch(action) 方法更新 state
    • 通過 subscribe(listener) 注冊監(jiān)視器
    • 通過 subscribe(listener) 返回的函數(shù)注銷監(jiān)聽器

源碼

類型

  • export type Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A) => S;

createStore

  1. 類型:Function

  2. 參數(shù):

    • reducer<Reducer>
    • 【可選】preloadedState<PreloadedState<S>> => 初始化 state
    • 【可選】enhancer<StoreEnhancer<Ext, StateExt>> => 增強器,用來擴展store的功能
  3. 返回值:store<Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext>

源碼分析

源碼
  • 1 - 10:preloadedStateenhancer 不能同時為 Function
  • 12 - 15:如果 preloadedStateFunction 但是 enhancerundefined 那么將 preloadedState 賦給 enhancer,并且 preloadedState 置為 undefined
源碼
  • 1 - 10:如果 enhancer 不為 undefined,那么 enhancer 必須為 Function,如果是 Function 直接返回 enhancer(createStore)(reducer, preloadedState)。這個在 applyMiddleware 時具體講解
// TODO: 使用一個 enhancer 作為例子
  • 12 - 14: 如果 reducer 不是 Function 則拋錯
源碼
  • currentReducer => 傳入的 reducer
  • currentState => preloadedState => undefined | 用戶傳入的 state
  • currentListeners: (() => void)[] | null => [] => 存儲更新函數(shù)的數(shù)組
  • nextListeners => [] => 下次dispatch將會觸發(fā)的更新函數(shù)數(shù)組
  • isDispatching<boolean> => Lock,當前是否在 dispatch
源碼

定義 ensureCanMutateNextListeners、getState、subscribe、dispatch、replaceReducerobservable 函數(shù)

源碼
  • 2:dispatch 一個 ActionTypes.INITaction => 使得每個 reducer 返回初始 state
  • 4 - 11: 創(chuàng)建 store 變量并返回
dispatch
dispatch
  • 2 - 4:判斷 action 是否是純粹的對象。即不是 Array | Function | null,isPlainObject 定義
  • 6 - 8: action 必須有 type
  • 10 - 12:如果當前正在 dispatch 則不能 dispatch => 不可以同時 dispatch 兩個 action
  • 14 - 19:執(zhí)行 currentReducer,傳入 currentStateaction
  • 21 - 25:將 nextListeners 賦給 currentListenerslisteners,之后循環(huán)遍歷執(zhí)行 listener
    最終 return action
getState
getState

getState 方法比較簡單,就是 return currentState

subscribe
subscribe
  • 2 - 8:如果 listener 不是 Function 或者 isDispatching === true 拋錯
  • 10:定義?? isSubscribed => 是否已經(jīng)訂閱
  • 12: 執(zhí)行 ensureCanMutateNextListeners 函數(shù)
  • 13:將 listener 推入 nextListeners
  • 15:return unsubscribe 函數(shù)
  • 16 - 20:如果 isSubscribed === false 直接返回, 如果 isDispatching === true 拋錯
  • 22:關??
  • 24:執(zhí)行 ensureCanMutateNextListeners 函數(shù)
  • 25 - 27:在 nextListeners 中刪除 listener 函數(shù),并將 currentListeners 置為 null
ensureCanMutateNextListeners
ensureCanMutateNextListeners

currentListeners 做了淺拷貝以便于可以在 dispatch 的時候使用 nextListeners 作為臨時的 listener。這樣可以防止使用者在 dispatch 的時候調用 subscribe/unsubscribe 出現(xiàn) bug。避免相互影響

combineReducers

  1. 類型:Function
  2. 參數(shù):reducers
  3. 返回值:Function
  4. 使用 combineReducers 例子

源碼分析

源碼
  • 2 - 13:將用戶傳入的 reducers 做一個 shallow copy,并且剔除不是 Functionreducer。
    • finalReducers 為有效的 reducer
    • finalReducerKeys => 有效 reducerkey
  • 15 - 20:執(zhí)行 assertReducerShape 函數(shù),如有 ErrorError 賦值給 shapeAssertionError
  • 22:返回 combination 函數(shù)
assertReducerShape
assertReducerShape

遍歷給到的 reducer

  • 如果在初始化 action 時返回 undefined拋錯
  • 如果執(zhí)行一個隨機 action 時返回 undefined,拋錯
combination
combination
  • 2 - 4:如果 assertReducerShape 函數(shù)執(zhí)行時出錯,則拋出錯誤
  • 6 - 7: 定義 hasChanged ?? 和 nextState 變量
  • 8 - 19:遍歷 finalReducerKeys 數(shù)組,previousStateForKey = state[key],nextStateForKey = reducer(previousStateForKey, action),如果 nextStateForKeyundefined 拋錯。將 reducer 計算出來的 state 存入 nextState 中。并且判斷 previousStateForKeynextStateForKey 是否改變了
  • 20 - 21:判斷 finalReducerKeys 和傳入 statekeylength 是否改變。最終 return hasChanged ? nextState : state

compose

  1. 類型:Function
  2. 參數(shù):Function[]
  3. 返回值:Function
  4. 使用 compose 例子

源碼分析

compose
  • 2 - 5:如果數(shù)組為空,返回一個空函數(shù)
  • 7 - 9:如果數(shù)組的 length 為1,則返回第一個函數(shù)
  • 11:對函數(shù)數(shù)組進行 reduce 操作
    compose(increase, square, add) // (...args) => increase(sqpare(add(...args)))
    

注意:Array.reduce() 的使用 => 回調函數(shù)第一次執(zhí)行時,accumulatorcurrentValue 取值有兩種情況:如果調用 reduce() 時提供了 initialValue,accumulator 取值 initialValuecurrentValue 取數(shù)組中的第一個值;如果沒有提供 initialValue,那么 accumulator 取數(shù)組中第一個值,currentValue 取數(shù)組中的第二個值

Lodash compose -> flow

Lodash compose

applyMiddleware

  1. 類型:Function
  2. 參數(shù):Middleware[]
  3. 返回值:Function
  4. Middleware:({getState}) => next => dispatch => ({getState}) => dispatch => action => any

applyMiddleware的功能:改造dispatch函數(shù),產(chǎn)生真假dispatch,而中間件就是運行在假真(dispatchAndLog假和next真)之間的代碼。

源碼分析

applyMiddleware

applyMiddleware 函數(shù),直接返回一個函數(shù),我們直接看這個函數(shù)即可。這個函數(shù)的結構是 (createStore) => (reducer, preloadedState?) => store

  • 6:執(zhí)行 createStore 函數(shù),獲取 store
  • 7 - 12:定義 dispatch 函數(shù)
  • 14 - 17:定義 middlewareAPI 對象,這里 middlewareAPI 只有兩個屬性,getStatedispatch
  • 18:將用戶傳入的 middleware[] 依次執(zhí)行,并將 middlewareAPI 作為參數(shù)傳給各個 Middleware,Middleware 的例子如下
    function logger({ getState }) {
      return next => action => {
        console.log('will dispatch', action)
    
        const returnValue = next(action)
    
        console.log('state after dispatch', getState())
    
        return returnValue
      }
    }
    
    此時 chain 的值為 Middleware 返回值的數(shù)組
    // chain => [middleware1, middleware2, middleware3]
    next => action => {
        console.log('will dispatch', action)
    
        const returnValue = next(action)
    
        console.log('state after dispatch', getState())
    
        return returnValue
    }
    
  • 19: dispatch = compose<typeof dispatch>(...chain)(store.dispatch)。使用 compose 函數(shù),此時 Middleware 中的 next === store.dispatch
  • 21 - 24:return store,這里的 dispatch 是 19 行的 dispatch

之后看一下 Middleware 使用場所。是作為 applyMiddleware 的參數(shù)使用。applyMiddleware 做為 createStore 的第三個參數(shù)傳入 createStore。

createStore enhancer

此時的 enhancer === applyMiddleware,之后在 createStore 里面直接執(zhí)行
applyMiddleware,將 createStorereducer + preloadedState 傳入 applyMiddleware,即在 applyMiddleware 代碼中的第 21 行 - 第 24 行的返回值即為 createStore 的返回值

applyMiddleware 做了什么

  1. 執(zhí)行傳入的 Middleware,并將 state 作為參數(shù)傳入 Middleware
  2. 更新 dispatch,這個更新后的 dispatch 是用戶傳入的(強化的 dispatch),例如上述的 logger 例子
    action => {
        console.log('will dispatch', action)
    
        // 此處的 next === dispatch === store.dispatch === createStore(reducer, preloadedState).dispatch
        const returnValue = next(action)
    
        console.log('state after dispatch', getState())
    
        return returnValue
    }
    

注意點

  1. enhancer store 的增強器。enhancer 是一個高階函數(shù),返回值是一個經(jīng)過包裝的強化的 store。applyMiddleware 就是一個 enhancer

疑問

Redux 做了什么?

  1. 存儲 state
  2. 增刪改查 state => 可以理解為 state 的變化
  3. 觸發(fā)更新

middleware 的執(zhí)行順序

Redux 的中間件模型類似于 koa。在 next 前面以及 next,由外向內依次執(zhí)行。當最里面的 next 執(zhí)行完成之后,next 后面的代碼會由內向外執(zhí)行。非常類似于 Koa 的洋蔥中間件模型。

dispatch 之后,Redux 是如何去處理的?

  1. 執(zhí)行 reducer 更新 currentState
  2. 依次執(zhí)行 listener 函數(shù)

state 中的數(shù)據(jù)被修改之后,訂閱者們如何去收到更新后的數(shù)據(jù)?

subscribe 中通過 store.getState() 獲取數(shù)據(jù)

applyMiddlewaredispatch 為何賦值兩次

第一次賦值表示 => 在 Middleware 創(chuàng)建時,不能進行 dispatch
第二次賦值表示 => 此 dispatch 是一個強化后的 dispatch

middlewareAPI 中的 dispatch 什么時候會被調用

注意使用場景

function logger({ getState, dispatch }) {
  // 此時的 dispatch 為拋錯函數(shù),即第一次賦值的函數(shù),即第 7 - 12 行
  return next => {
    // 此處的 next === store.dispatch
    // 此時的 dispatch === 下面 return 的函數(shù)
    return action => {
        console.log('will dispatch', action)

        const returnValue = next(action)

        console.log('state after dispatch', getState())

        return returnValue
      }
  }
}

middlewareAPI 中的 dispatch 為啥要用匿名函數(shù)包裹

let dispatch = () => console.log('Error!');
const obj = {
  name: 'dispatch',
  dispatch,
}
obj.dispatch();

dispatch = () => console.log('Ready!');
obj.dispatch();

上述的 log 是什么結果呢?


result
let dispatch = () => console.log('Error!');
const obj = {
  name: 'dispatch',
  dispatch: (...args) => dispatch(...args),
}
obj.dispatch();

dispatch = () => console.log('Ready!');
obj.dispatch();

上述的 log 是什么結果呢?


result

store.subscribe 為啥要有?? isDispatching

dispatch 執(zhí)行時候會循環(huán)執(zhí)行更新函數(shù),要保證 listeners 數(shù)組在這時候不能被改變

以下代碼作用

createStore

答:有了這一層判斷,我們就可以這樣傳:createStore(reducer, initialState, enhancer) 或者這樣:createStore(reducer, enhancer),其中 enhancer 還會是 enhancer。

Redux 優(yōu)勢

  1. 純函數(shù),做測試的時候 easy
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉變要...
    余生動聽閱讀 10,871評論 0 11
  • 彩排完,天已黑
    劉凱書法閱讀 4,483評論 1 3
  • 表情是什么,我認為表情就是表現(xiàn)出來的情緒。表情可以傳達很多信息。高興了當然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,700評論 2 7

友情鏈接更多精彩內容