一起學(xué)react(3) redux源碼分析

今天來討論一下redux源碼部分
先附上demo 如果大家有問題 可以直接加我QQ:469373256
demo地址:https://github.com/fangkyi03/redux-demo.git

  • demo
function funtest(state,action){
    console.log('state',state,action)
    switch (action.type) {
        case 'test':
            return Object.assign(state,{test:action.payload})
        default:
            return state || {}  
    }
}

const fun1 = (store) => {
    console.log('store',store);
    return (next) =>{
        console.log('next',next)
        return (action) =>{
            next(action)
            console.log('action',action);
        }
    }
}

const fun2 = (store) => {
    console.log('store',store);
    return (next) =>{
        console.log('next',next)
        return (action) =>{
            next(action)
            console.log('action',action);
        }
    }
}

const store = createStore(combineReducers({funtest}),{a:1},applyMiddleware(fun1,fun2,fun1))
console.log('輸出state',store.getState());
console.log('輸出action',store.dispatch({type:'test',payload:'asdasd'}));
console.log('輸出state',store.getState())

store.subscribe(()=>{
    console.log('輸出store最新變化',store.getState());
})
  • 暴露的接口部分
export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}

redux核心暴露出來的接口只有這么幾個(gè) 接下來將會(huì)按照這個(gè)demo的執(zhí)行順序一一的給大家進(jìn)行一下解釋 先來看一下combineReducers這個(gè)函數(shù)的使用方法

  • combineReducers
    先附上完整代碼 然后在一一講解
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
  }
}

combineReducers主要可以分為兩個(gè)部分
1.效驗(yàn)部分 效驗(yàn)?zāi)爿斎氲膮?shù)是否符合要求,如果不符合就會(huì)在使用之前直接拋出異常

  1. combination部分 也就是在每次dispatch的時(shí)候 都會(huì)執(zhí)行的這個(gè)
    在這里會(huì)傳入最新的state跟action 每次都會(huì)重新的去執(zhí)行一遍 并且返回最新的一個(gè)currentState值 這里也是比較詬病的地方 因?yàn)槿绻愕膔educe比較多的情況下 你每次dispatch都會(huì)去將所有的reduce重新去走一遍 其實(shí)是比較耗費(fèi)性能的
    將效驗(yàn)可以分成如下幾種情況

1.當(dāng)執(zhí)行reduce以后 返回的值為undefinde的時(shí)候
在這里會(huì)調(diào)用兩種模式 一種是直接傳遞type為init的 一種是傳遞type為隨機(jī)數(shù)的
其實(shí)主要就是確保你在switch的時(shí)候 都加了default類型 避免 出現(xiàn)返回undefined的情況

function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    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. If you don't want to set a value for this reducer, ` +
        `you can use null instead of undefined.`
      )
    }

    const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    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, but can be null.`
      )
    }
  })
}

2.如果發(fā)現(xiàn)state不等于對(duì)象的時(shí)候報(bào)錯(cuò)

if (!isPlainObject(inputState)) {
    return (
      `The ${argumentName} has unexpected type of "` +
      ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
      `". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    )
  }

3.如果在新傳入的state與第一次初始化時(shí)init返回的節(jié)點(diǎn)不一致的時(shí)候 會(huì)直接進(jìn)行錯(cuò)誤提醒 但是并不會(huì)中斷程序的運(yùn)行

  const unexpectedKeys = Object.keys(inputState).filter(key =>
    !reducers.hasOwnProperty(key) &&
    !unexpectedKeyCache[key]
  )

  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })

  if (unexpectedKeys.length > 0) {
    return (
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
      `Expected to find one of the known reducer keys instead: ` +
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    )
  }
}

4.如果在combination的運(yùn)算過程中發(fā)現(xiàn)返回的state如果是一個(gè)undefined的話會(huì)直接報(bào)錯(cuò) 這個(gè)錯(cuò)誤會(huì)直接中斷程序的運(yùn)行 所以reduce的要求就是 任何情況下 都不能返回一個(gè)空數(shù)據(jù)

    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
  }

這里正常應(yīng)該講中間件了 但是這邊稍微等一下 因?yàn)橐瓤匆幌耤reateStore中是如何處理的 才能繼續(xù)講applyMiddleware

  • createStore部分
參數(shù)定義
export default function createStore(reducer, preloadedState, enhancer) 

如果preloadedState初始化state是一個(gè)函數(shù) 并且enhancer值為空的情況下 那么就將preloadedState當(dāng)做中間件處理來使用
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }


如果enhancer中間件函數(shù)不等于空 并且不等于函數(shù)的話 就直接報(bào)錯(cuò)
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }
  
如果reducer不等于函數(shù)則直接報(bào)錯(cuò)
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

從這里可以看到createStore有兩種寫法
createStore(reducer, preloadedState, enhancer)
createStore(reducer, enhancer)
兩者的區(qū)別是一種帶了默認(rèn)state一種不帶
當(dāng)然一般用了ssr的話 都會(huì)采用第一種 因?yàn)檫@樣會(huì)更加方便初始化state導(dǎo)入

上面我們看到 如果你有傳入applyMiddleware中間件的話 默認(rèn)會(huì)走中間件 并且將我們自身給傳遞過去 那么我們再看一下中間件的寫法
  • 中間件完整代碼
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    這里傳遞了原有的createStore過來以后 又使用它去進(jìn)行了一次初始化
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    這里兩句話 才是redux最為經(jīng)典的地方 將這兩個(gè)地方就得先看一下中間件的結(jié)構(gòu)
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
  • 中間件代碼結(jié)構(gòu)
const fun1 = (store) => {
    console.log('store',store);
    return (next) =>{
        console.log('next',next)
        return (action) =>{
            next(action)
            console.log('action',action);
        }
    }
}

const fun2 = (store) => {
    console.log('store',store);
    return (next) =>{
        console.log('next',next)
        return (action) =>{
            next(action)
            console.log('action',action);
        }
    }
}

按照執(zhí)行順序來的話 如果你執(zhí)行了這個(gè)以后
chain = middlewares.map(middleware => middleware(middlewareAPI))
實(shí)際上返回的是中間件的這部分以后的代碼 并且會(huì)給所有的中間件傳遞兩個(gè)函數(shù)一個(gè)dispatch 一個(gè)getState

    return (next) =>{
        console.log('next',next)
        return (action) =>{
            next(action)
            console.log('action',action);
        }
    }
  • compose(...chain)(store.dispatch)
    這個(gè)部分其實(shí)分為幾種類型 1.沒有數(shù)據(jù),2.單條數(shù)據(jù) 3.多條數(shù)據(jù)
    來看一下compose的源碼
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

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

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

1.如果你中間件里面沒有數(shù)據(jù)的話 最走這里返回的arg其實(shí)就是你傳進(jìn)去的dispatch

  if (funcs.length === 0) {
    return arg => arg
  }

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

2.如果你傳進(jìn)去的長度為1的話 說明只有一條數(shù)據(jù) 那么這里就直接返回對(duì)應(yīng)的那個(gè)數(shù)據(jù)

  if (funcs.length === 1) {
    return funcs[0]
  }
剛才我們上面已經(jīng)提到了 我們在
middlewares.map(middleware => middleware(middlewareAPI))
執(zhí)行完以后 我們所有返回的值 其實(shí)都已經(jīng)在next子函數(shù)下面了
所以當(dāng)我們在這里直接return funcs[0]的時(shí)候 實(shí)際上就是返回我們的next
dispatch = compose(...chain)(store.dispatch)
因?yàn)檫@句代碼的關(guān)系 我們返回的next被執(zhí)行了 并且傳入了store.dispatch這個(gè)參數(shù) 然后返回了next里面包裹的action函數(shù) 作為新的dispatch函數(shù)
所以從這里可以看到 單條數(shù)據(jù)的是next等于傳入的store.dispatch
所以你可以在接收到action以后 直接next(action)這種方式 直接讓他走對(duì)應(yīng)的reduce

3.多條數(shù)據(jù)跟單條的有一點(diǎn)差別 單條數(shù)據(jù)的時(shí)候 next 永遠(yuǎn)是dispatch 但是如果多條的時(shí)候 next就成為了 控制你下一個(gè)中間件是否執(zhí)行的關(guān)鍵 因?yàn)閚ext在多條里面 傳入的不單單是dispatch了 還有可能是你上一個(gè)中間件的next部分

return funcs.reduce((a, b) => (...reset) => a(b(...reset)))
如果你只有兩條數(shù)據(jù)的時(shí)候 這個(gè)...reset依舊是你傳遞進(jìn)去的store.dispatch
如果你在多余兩條數(shù)據(jù)傳遞進(jìn)去以后 這里的...reset在第一次還是store.dispatch
在第二次就變成了
return (next) =>{
        console.log('next',next)
        return (action) =>{
            next(action)
            console.log('action',action);
        }
 }
在這里是不是覺得有點(diǎn)繞 感覺這樣的操作 最后是這么返回一個(gè)dispatch的呢
funcs.reduce((a, b) => (...reset) => a(b(...reset)))
在這個(gè)階段 其實(shí)永遠(yuǎn)不知道傳進(jìn)去的action到底是什么 這里只是生成了一個(gè)嵌套的函數(shù)鏈 最后生成的一個(gè)函數(shù) 會(huì)在每次dispatch的時(shí)候 都將所有的中間件全部的執(zhí)行一次 知道最后一層使用了dispatch的時(shí)候 才算是結(jié)束 了
dispatch((action)=>{
  return a(b(...arg))(action)
})

ok 現(xiàn)在redux最核心的部分就講完了 接下來講一下回調(diào)監(jiān)聽的部分

  • subscribe
    這個(gè)部分其實(shí)比較簡單 就是你傳入的任何一個(gè)listener只要是一個(gè)函數(shù)就會(huì) 被加入到監(jiān)聽的數(shù)組里面 然后返回一個(gè)卸載的函數(shù) 那么這個(gè)監(jiān)聽函數(shù)又是在哪里被使用的呢 這里就得看一下createStore中關(guān)于dispatch的定義了
 function subscribe(listener) {
   if (typeof listener !== 'function') {
     throw new Error('Expected listener to be a function.')
   }

   let isSubscribed = true

   ensureCanMutateNextListeners()
   nextListeners.push(listener)

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

     isSubscribed = false

     ensureCanMutateNextListeners()
     const index = nextListeners.indexOf(listener)
     nextListeners.splice(index, 1)
   }
 }
  • dispatch定義
    我們在講剛才的compose的時(shí)候 有說過 你可以在中間件里面直接用dispatch 也可以直接使用傳進(jìn)去的那個(gè)store.dispatch 甚至 你也可以不執(zhí)行next來阻止接下來的中間件執(zhí)行 不管如何情況 如果在中間件所有都符合的情況下 這個(gè)dispatch肯定會(huì)被執(zhí)行 如果你當(dāng)前中間件在運(yùn)行過程中 在currentReducer還沒執(zhí)行完畢的情況下 你又去發(fā)起了一條dispatch的話 是會(huì)導(dǎo)致奔潰報(bào)錯(cuò)的
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
  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++) {
      const listener = listeners[i]
      listener()
    }
    return action
  }
  • dispatch 主要看兩個(gè)部分 currentReducer與listeners
    在這里要說兩點(diǎn) 這里其實(shí)也是redux性能最大的一個(gè)問題所在
    當(dāng)你每次執(zhí)行dispatch的時(shí)候 都會(huì)去生成一個(gè)最新的reducer并且還會(huì)將所有的監(jiān)聽都去執(zhí)行一遍 其實(shí)這里耗費(fèi)的性能是很大的 這里的currentReducer就是combination
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
  • 參數(shù)定義部分
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

在這里 redux源碼 基本分析完畢 如果大家有問題 可以直接加我QQ:469373256

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末添瓷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子璧针,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件葵擎,死亡現(xiàn)場離奇詭異,居然都是意外死亡半哟,警方通過查閱死者的電腦和手機(jī)酬滤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門签餐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盯串,你說我怎么就攤上這事氯檐。” “怎么了体捏?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵冠摄,是天一觀的道長。 經(jīng)常有香客問我几缭,道長河泳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任年栓,我火速辦了婚禮拆挥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘某抓。我一直安慰自己纸兔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布否副。 她就那樣靜靜地躺著食拜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪副编。 梳的紋絲不亂的頭發(fā)上负甸,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音痹届,去河邊找鬼呻待。 笑死,一個(gè)胖子當(dāng)著我的面吹牛队腐,可吹牛的內(nèi)容都是我干的蚕捉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼柴淘,長吁一口氣:“原來是場噩夢啊……” “哼迫淹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起为严,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤敛熬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后第股,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體应民,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诲锹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片繁仁。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幕屹,死狀恐怖采够,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情白热,我是刑警寧澤庸诱,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布悬钳,位于F島的核電站,受9級(jí)特大地震影響偶翅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碉渡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一聚谁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滞诺,春花似錦形导、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淋叶,卻和暖如春阎曹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背煞檩。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工处嫌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斟湃。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓熏迹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親凝赛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子注暗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • 前言 本文 有配套視頻,可以酌情觀看墓猎。 文中內(nèi)容因各人理解不同捆昏,可能會(huì)有所偏差,歡迎朋友們聯(lián)系我討論毙沾。 文中所有內(nèi)...
    珍此良辰閱讀 11,894評(píng)論 23 111
  • 學(xué)習(xí)必備要點(diǎn): 首先弄明白屡立,Redux在使用React開發(fā)應(yīng)用時(shí),起到什么作用——狀態(tài)集中管理 弄清楚Redux是...
    賀賀v5閱讀 8,877評(píng)論 10 58
  • 看到這篇文章build an image gallery using redux saga,覺得寫的不錯(cuò)膨俐,長短也適...
    smartphp閱讀 6,141評(píng)論 1 29
  • 本文是關(guān)于 redux(3.7.2)源代碼的一些淺讀 在redux源碼目錄中 勇皇,可以看到以下文件目錄: 與文件對(duì)應(yīng)...
    孫焱焱閱讀 439評(píng)論 0 1
  • 考研成績出來了敛摘,難過的沒考上自己心儀的學(xué)校,成績尷尬的卡在國家線上乳愉。此刻心情復(fù)雜兄淫,雖說有心理準(zhǔn)備,但還是忍不住的失...
    了望臺(tái)閱讀 198評(píng)論 0 0