初步解讀 Redux 源碼理解其原理

Redux github 地址

前言

Redux 源碼的體量算是比較小的了,但是詳細(xì)解讀還是有點(diǎn)壓力蛋哭,在此簡(jiǎn)單解讀下县习,目的是能大概理解其原理。直接看導(dǎo)出的 API:createStore谆趾、combineReducers躁愿、bindActionCreatorsapplyMiddleware沪蓬、compose彤钟,其中 bindActionCreators 暫時(shí)不想理會(huì),余下的依次介紹怜跑。

createStore

關(guān)鍵代碼(省去一些加強(qiáng)健壯性的代碼)以及對(duì)應(yīng)的解讀注釋如下:

// 函數(shù)接受三個(gè)參數(shù)样勃,第一個(gè)是 reducer吠勘,第二個(gè)是初始 state,第三個(gè)是由 applyMiddleware
// 生成的 enhancer
export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState) // 可以看出 enhancer
    // 是一個(gè)以 createStore 為參數(shù)然后返回一個(gè) createStore 的高階函數(shù)峡眶,待會(huì)兒通過(guò)
    // applyMiddleware 可以看到這個(gè) enhancer 到底是怎么起作用的
  }
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  function getState() { // 返回當(dāng)前的 state
    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
  }

  function subscribe(listener) { // 訂閱函數(shù)剧防,每當(dāng) dispatch 的時(shí)候 listeners 都會(huì)執(zhí)行
    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)

    // 返回值是一個(gè)可以取消當(dāng)前 listener 訂閱的函數(shù)
    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)
    }
  }

  // dispatch,通過(guò)這個(gè)函數(shù) dispatch action 借由 reducers 來(lái)生成一個(gè)新的 state
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) { // 如前所述辫樱,每 dispatch 一個(gè) action 時(shí)
      // 都會(huì)去執(zhí)行所有的 listeners
      const listener = listeners[i]
      listener()
    }

    return action // 返回值和傳入的參數(shù)一樣峭拘,action
  }

  function replaceReducer(nextReducer) { // 替換 reducer
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    // replace reducer 后 dispatch  一個(gè) REPLACE action
    dispatch({ type: ActionTypes.REPLACE })
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  // store 生成后 dispatch 一個(gè) INIT 的 action
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  }
}

createStore 主要的作用就是根據(jù)提供的 reducerinitialStateenhancer 生成 store狮暑,然后 store 可以提供 dispatch鸡挠、subscribegetState搬男、replaceReducer等方法拣展。

combineReducers

combineReducer 的作用是將多個(gè)(如果有的話) reducer 整合成一個(gè)總的 reducer,關(guān)鍵代碼:

// reducers 是一個(gè) plain object缔逛,一個(gè) key 對(duì)應(yīng)一個(gè) reducer
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    // 過(guò)濾無(wú)效的 reducer
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // 返回值依然是一個(gè)以 state 和 action 為參數(shù)的 reducer备埃,但是它可以處理所有的 type 的 action
  return function combination(state = {}, action) {

    let hasChanged = false
    const nextState = {}
    // 具體處理 action 的做法是,把每個(gè) action 代入所有 reducer 生成對(duì)應(yīng)的結(jié)果褐奴,然后再整合
    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)

      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

compose

這個(gè)方法的代碼最少:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

funcs是一個(gè)函數(shù)數(shù)組按脚,reduce是數(shù)組的歸并方法。這個(gè)方法接受一系列的函數(shù)作為參數(shù)敦冬,而且這一系列函數(shù)辅搬,從右到左,上一個(gè)函數(shù)的返回值可以為下一個(gè)函數(shù)的參數(shù)脖旱,最終返回一個(gè)以最右邊的函數(shù)的參數(shù)為參數(shù)(這個(gè)參數(shù)再依次交給左邊的函數(shù)處理堪遂,返回后繼續(xù)此步驟,一直到最左邊)以最左邊的函數(shù)的返回值為返回值的復(fù)合函數(shù)萌庆。比如:

 const F = compose(f1, f2. f3, f4, ..., fn)
 F //f1(f2...(fn(...args)))

applyMiddleware

applyMiddleware蚤氏,可以說(shuō)這個(gè)方法為 Redux 提供了各種可能性,關(guān)鍵代碼:

import compose from './compose'

export default function applyMiddleware(...middlewares) {
  // 返回值是一個(gè)同時(shí)以 createStore 為參數(shù)和返回值的閉包
  return createStore => (...args) => {
    const store = createStore(...args) // 這里的store與沒(méi)有enhancer時(shí)的并無(wú)二致
    let dispatch = () => {
      throw new Error( // 中間件中不允許 dispatch
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    
    // 中間件API踊兜,規(guī)定了 middleware 是一個(gè)以 { getState, dispatch } 為參數(shù)的函數(shù)
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }

    // chain 的每個(gè)元素依然是一個(gè)函數(shù)竿滨,經(jīng)過(guò) compose 作用后返回一個(gè)合成函數(shù),合成函數(shù)以
    // store.dispatch 為參數(shù),最終生成一個(gè)加強(qiáng)了的 dispatch
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)
    // 上面語(yǔ)句分開(kāi)寫就是 const composedFn = compose(...chain)
    // dispatch = composedFn(store.dispatch) 
    // 其中 composedFn: (...args) => chain[0](chain[1](...(chain[length - 1](...args))))
    // 可以看到捏境,`createStore` 經(jīng)過(guò) `storeEnhancer` 加強(qiáng)之后于游,其實(shí)只是用新的`dispatch` 將原來(lái)
    // 的 `dispatch` 替換,其他的部分保持不變
    return {
      ...store,
      dispatch
    }
  }
}

這個(gè)方法接受一系列的 Redux 的 middleware 為參數(shù)垫言,然后返回一個(gè)以 createStore 為參數(shù)的 storeEnhancer贰剥,其實(shí) storeEnhancer enhance 的是 dispatch(這里好像并不準(zhǔn)確,因?yàn)槌擞芍虚g件生成的 storeEnhancer 以外筷频,還有其他的 storeEnhancer蚌成,而這些 storeEnhancer 就有更強(qiáng)的功能前痘,比如像 devToolsExtension 這樣的擴(kuò)展工具)。由于每個(gè) middleware 在作用 { getState, dispatch } 后可以被 compose 處理担忧,那我們可以知道 middleware({ getState, dispatch }) 的返回值是一個(gè)函數(shù)芹缔,而且這個(gè)函數(shù)的參數(shù)和返回值是具有相同簽名的函數(shù),于是 middleware 的函數(shù)簽名大概是:({ getState, dispatch }) => next => action瓶盛,其中 next(action) 表示將 action 交由下一個(gè) middleware 處理最欠,最后一個(gè) middleware 的 nextdispatch

舉個(gè)例子:

//m1, m2, m3 是三個(gè)中間件
const middlewares = [m1, m2, m3]

const storeEnhancer = applyMiddleWare(...middlewares)
const store = createStore(reducer, {}, storeEnhancer)

export default store

store.dispatch 被強(qiáng)化的過(guò)程是這樣:
普通 dispatch -> 被 m3 強(qiáng)化后的 dispatch(記為 m3(dispatch)) -> 再被 m2 強(qiáng)化后的 dispatch(記為 m2(m3(dispatch))) -> 再被 m1 強(qiáng)化后的 dispatch(記為 m1(m2(m3(dispatch)))惩猫。
對(duì)應(yīng)地芝硬,加載了上述中間件的 store dispatch 一個(gè) action 的過(guò)程是這樣:
m1(m2(m3(dispatch)))(action) -> next(action)next === m2(m3(dispatch)))-> next(action)next === m3(dispacth))-> next(action)next === dispatch)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末轧房,一起剝皮案震驚了整個(gè)濱河市拌阴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奶镶,老刑警劉巖皮官,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異实辑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)藻丢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門剪撬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人悠反,你說(shuō)我怎么就攤上這事残黑。” “怎么了斋否?”我有些...
    開(kāi)封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵梨水,是天一觀的道長(zhǎng)享甸。 經(jīng)常有香客問(wèn)我迄薄,道長(zhǎng),這世上最難降的妖魔是什么鳍鸵? 我笑而不...
    開(kāi)封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任旦委,我火速辦了婚禮奇徒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缨硝。我一直安慰自己摩钙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布查辩。 她就那樣靜靜地躺著胖笛,像睡著了一般网持。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上长踊,一...
    開(kāi)封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天功舀,我揣著相機(jī)與錄音,去河邊找鬼之斯。 笑死日杈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的佑刷。 我是一名探鬼主播莉擒,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瘫絮!你這毒婦竟也來(lái)了涨冀?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤麦萤,失蹤者是張志新(化名)和其女友劉穎鹿鳖,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體壮莹,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翅帜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了命满。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涝滴。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖胶台,靈堂內(nèi)的尸體忽然破棺而出歼疮,到底是詐尸還是另有隱情,我是刑警寧澤诈唬,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布韩脏,位于F島的核電站,受9級(jí)特大地震影響铸磅,放射性物質(zhì)發(fā)生泄漏赡矢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一阅仔、第九天 我趴在偏房一處隱蔽的房頂上張望济竹。 院中可真熱鬧,春花似錦霎槐、人聲如沸送浊。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)袭景。三九已至唁桩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耸棒,已是汗流浹背荒澡。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留与殃,地道東北人单山。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像幅疼,于是被迫代替她去往敵國(guó)和親米奸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 在學(xué)習(xí)了redux過(guò)程中爽篷,了解到中間件這個(gè)名詞悴晰,但是我看了十遍,也完全就是懵逼的狀態(tài)逐工。于是又重復(fù)敲了幾次代碼也不能...
    綽號(hào)陸拾柒閱讀 532評(píng)論 0 0
  • 看到這篇文章build an image gallery using redux saga铡溪,覺(jué)得寫的不錯(cuò),長(zhǎng)短也適...
    smartphp閱讀 6,156評(píng)論 1 29
  • 前言 本文 有配套視頻泪喊,可以酌情觀看棕硫。 文中內(nèi)容因各人理解不同,可能會(huì)有所偏差袒啼,歡迎朋友們聯(lián)系我討論哈扮。 文中所有內(nèi)...
    珍此良辰閱讀 11,906評(píng)論 23 111
  • 上周六參加了一個(gè)公司的面試,因?yàn)槭呛荛L(zhǎng)時(shí)間以來(lái)的第一次面試瘤泪,發(fā)揮的并不是很好,有兩個(gè)下來(lái)一想就明白的問(wèn)題在當(dāng)時(shí)卻卡...
    夏爾先生閱讀 6,417評(píng)論 0 15
  • 很久以前育八,一座非常茂密的森林里对途,住著一只貓頭鷹。它不知道為了什么髓棋,竟開(kāi)始為別人操心实檀。 因此,它開(kāi)始沉思獅子憑它的權(quán)...
    我要不停的奔跑閱讀 638評(píng)論 0 0