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陆赋、subscribedispatch己莺、replaceReducerobservable 函數(shù)

源碼
  • 2:dispatch 一個 ActionTypes.INITaction => 使得每個 reducer 返回初始 state
  • 4 - 11: 創(chuàng)建 store 變量并返回
dispatch
dispatch
  • 2 - 4:判斷 action 是否是純粹的對象奏甫。即不是 Array | Function | nullisPlainObject 定義
  • 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)系作者
  • 序言:七十年代末仇让,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子躺翻,更是在濱河造成了極大的恐慌丧叽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件公你,死亡現(xiàn)場離奇詭異踊淳,居然都是意外死亡,警方通過查閱死者的電腦和手機省店,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門嚣崭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人懦傍,你說我怎么就攤上這事雹舀。” “怎么了粗俱?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵说榆,是天一觀的道長。 經(jīng)常有香客問我寸认,道長签财,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任偏塞,我火速辦了婚禮唱蒸,結果婚禮上,老公的妹妹穿的比我還像新娘灸叼。我一直安慰自己神汹,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布古今。 她就那樣靜靜地躺著屁魏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捉腥。 梳的紋絲不亂的頭發(fā)上氓拼,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音,去河邊找鬼桃漾。 笑死坏匪,一個胖子當著我的面吹牛,可吹牛的內容都是我干的呈队。 我是一名探鬼主播剥槐,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宪摧!你這毒婦竟也來了粒竖?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤几于,失蹤者是張志新(化名)和其女友劉穎蕊苗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沿彭,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡朽砰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了喉刘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞧柔。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖睦裳,靈堂內的尸體忽然破棺而出造锅,到底是詐尸還是另有隱情,我是刑警寧澤廉邑,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布哥蔚,位于F島的核電站,受9級特大地震影響蛛蒙,放射性物質發(fā)生泄漏糙箍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一牵祟、第九天 我趴在偏房一處隱蔽的房頂上張望深夯。 院中可真熱鬧,春花似錦诺苹、人聲如沸咕晋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捡需。三九已至办桨,卻和暖如春筹淫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工损姜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饰剥,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓摧阅,卻偏偏與公主長得像汰蓉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子棒卷,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容

  • 今天感恩節(jié)哎顾孽,感謝一直在我身邊的親朋好友。感恩相遇比规!感恩不離不棄若厚。 中午開了第一次的黨會,身份的轉變要...
    迷月閃星情閱讀 10,551評論 0 11
  • 彩排完蜒什,天已黑
    劉凱書法閱讀 4,187評論 1 3
  • 表情是什么测秸,我認為表情就是表現(xiàn)出來的情緒。表情可以傳達很多信息灾常。高興了當然就笑了霎冯,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 124,187評論 2 7