React學(xué)習(xí)之深入Redux應(yīng)用框架

Redux作為大型React應(yīng)用狀態(tài)管理最常用的工具搬设。它是一個(gè)應(yīng)用數(shù)據(jù)流框架,與Flux框架類似膀捷。它是零依賴的迈嘹,可以配合其他框架或者類庫一起使用。雖然在平時(shí)的工作中很多次的用到了它全庸,但是一直沒有對其原理進(jìn)行研究秀仲。最近看了一下源碼,下面是我自己的一些簡單認(rèn)識壶笼。

  1. createStore
    結(jié)合使用場景我們首先來看一下createStore方法神僵。
// 這是我們平常使用時(shí)創(chuàng)建store
const store = createStore(reducers, state, enhance);

以下源碼為去除異常校驗(yàn)后的源碼,


export default function createStore(reducer, preloadedState, enhancer) {

// 如果有傳入合法的enhance覆劈,則通過enhancer再調(diào)用一次createStore
 if (typeof enhancer !== 'undefined') {
   if (typeof enhancer !== 'function') {
     throw new Error('Expected the enhancer to be a function.')
   }
   return enhancer(createStore)(reducer, preloadedState) // 這里涉及到中間件保礼,后面介紹applyMiddleware時(shí)在具體介紹
 }

 let currentReducer = reducer     //把 reducer 賦值給 currentReducer
 let currentState = preloadedState   //把 preloadedState 賦值給 currentState
 let currentListeners = []     //初始化監(jiān)聽函數(shù)列表
 let nextListeners = currentListeners   //監(jiān)聽列表的一個(gè)引用
 let isDispatching = false  //是否正在dispatch

 function ensureCanMutateNextListeners() {}

 function getState() {}

 function subscribe(listener) {}
 
 function dispatch(action) {}

 function replaceReducer(nextReducer) {}
 
// 在 creatorStore 內(nèi)部沒有看到此方法的調(diào)用,就不講了
 function observable() {}
 //初始化 store 里的 state tree
 dispatch({ type: ActionTypes.INIT })

 return {
   dispatch,
   subscribe,
   getState,
   replaceReducer,
   [$$observable]: observable
 }
}

我們可以看到creatorStore方法除了返回我們常用的方法外责语,還做了一次初始化過程dispatch({ type: ActionTypes.INIT });那么dispatch干了什么事情呢炮障?

/**
  * dispath action。這是觸發(fā) state 變化的惟一途徑坤候。
  * @param {Object} 一個(gè)普通(plain)的對象胁赢,對象當(dāng)中必須有 type 屬性
  * @returns {Object} 返回 dispatch 的 action
  */
 function dispatch(action) {
 // 判斷 dispahch 正在運(yùn)行,Reducer在處理的時(shí)候又要執(zhí)行 dispatch
   if (isDispatching) {
     throw new Error('Reducers may not dispatch actions.')
   }

   try {
     //標(biāo)記 dispatch 正在運(yùn)行
     isDispatching = true
     //執(zhí)行當(dāng)前 Reducer 函數(shù)返回新的 state
     currentState = currentReducer(currentState, action)
   } finally {
     isDispatching = false
   }
   const listeners = (currentListeners = nextListeners)
   //遍歷所有的監(jiān)聽函數(shù)
   for (let i = 0; i < listeners.length; i++) {
     const listener = listeners[i]
     listener() // 執(zhí)行每一個(gè)監(jiān)聽函數(shù)
   }
   return action
 }

這里dispatch主要做了二件事情

  • 通過reducer更新state
  • 執(zhí)行所有的監(jiān)聽函數(shù)白筹,通知狀態(tài)的變更
    那么reducer是怎么改變state的呢智末?這就涉及到下面的combineReducers了。
  1. combineReducers
    回到上面創(chuàng)建store的參數(shù)reducers遍蟋,
// 兩個(gè)reducer
const todos = (state = INIT.todos, action) => {
  // ....
};
const filterStatus = (state = INIT.filterStatus, action) => {
  // ...
};

const reducers = combineReducers({
  todos,
  filterStatus
});
// 這是我們平常使用時(shí)創(chuàng)建store
const store = createStore(reducers, state, enhance);

下面我們來看combineReducers做了什么

   // 第一次篩選吹害,參數(shù)reducers為Object
  // 篩選掉reducers中不是function的鍵值對
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)
  
  // 二次篩選,判斷reducer中傳入的值是否合法(!== undefined)
  // 獲取篩選完之后的所有key
  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    let hasChanged = false
    const nextState = {}
    // 遍歷所有的key和reducer虚青,分別將reducer對應(yīng)的key所代表的state它呀,代入到reducer中進(jìn)行函數(shù)調(diào)用
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // 這里就是reducer function的名稱和要和state同名的原因,傳說中的黑魔法
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 將reducer返回的值填入nextState
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 發(fā)生改變了返回新的nextState,否則返回原先的state
    return hasChanged ? nextState : state
  }
}
  • 這里 reducer(previousStateForKey, action)執(zhí)行的就是我們上面定義的todos和filterStatus方法纵穿。通過這二個(gè)reducer改變state值下隧。
  • 以前我一直很奇怪我們的reducer里的state是怎么做到取當(dāng)前reducer對應(yīng)的數(shù)據(jù)∥矫剑看到const previousStateForKey = state[key]這里我就明白了淆院。
  • 這里還有一個(gè)疑問點(diǎn)就是combineReducers的嵌套,最開始也我不明白句惯,看了源碼才知道combineReducers()=> combination(state = {}, action)土辩,這里combineReducers返回的combination也是接受(state = {}, action)也就是一個(gè)reducer所以可以正常嵌套。
    看到這初始化流程已經(jīng)走完了抢野。這個(gè)過程我們認(rèn)識了dispatch和combineReducers;接下來我們來看一下我們自己要怎么更新數(shù)據(jù)拷淘。

用戶更新數(shù)據(jù)時(shí),是通過createStore后暴露出來的dispatch方法來觸發(fā)的指孤。dispatch 方法启涯,是 store 對象提供的更改 currentState 這個(gè)閉包變量的唯一建議途徑(注意這里是唯一建議途徑,不是唯一途徑恃轩,因?yàn)橥ㄟ^getState獲取到的是state的引用结洼,所以是可以直接修改的。但是這樣就不能更新視圖了)叉跛。
正常情況下我們只需要像下面這樣

//action creator
var addTodo = function(text){
    return {
        type: 'add_todo',
        text: text
    };
};
function TodoReducer(state = [], action){
    switch (action.type) {
        case 'add_todo':
            return state.concat(action.text);
        default:
            return state;
    }
};

// 通過 store.dispatch(action) 來達(dá)到修改 state 的目的
// 注意: 在redux里,唯一能夠修改state的方法,就是通過 store.dispatch(action)
store.dispatch({type: 'add_todo', text: '讀書'});// 或者下面這樣
// store.dispatch(addTodo('讀書'));

也就是說dispatch接受一個(gè)包含type的對象松忍。框架為我們提供了一個(gè)創(chuàng)建Action的方法bindActionCreators昧互。

  1. bindActionCreators
    下面來看下源碼
// 核心代碼挽铁,并通過apply將this綁定起來
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
// 如果actionCreators是一個(gè)函數(shù)伟桅,則說明只有一個(gè)actionCreator敞掘,就直接調(diào)用bindActionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  // 遍歷對象,然后對每個(gè)遍歷項(xiàng)的 actionCreator 生成函數(shù)楣铁,將函數(shù)按照原來的 key 值放到一個(gè)對象中玖雁,最后返回這個(gè)對象
  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') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

bindActionCreators的作用就是使用dispatch把a(bǔ)ction creator包裹起來,這樣我們就可以直接調(diào)用他們了盖腕。這個(gè)在平常開發(fā)中不常用赫冬。

  1. applyMiddleware
    最后我們回頭來看一下之前調(diào)到的中間件,
import thunkMiddleware from 'redux-thunk';
// 兩個(gè)reducer
const todos = (state = INIT.todos, action) => {
  // ....
};
const filterStatus = (state = INIT.filterStatus, action) => {
  // ...
};

const reducers = combineReducers({
  todos,
  filterStatus
});
// 這是我們平常使用時(shí)創(chuàng)建store
const store = createStore(reducers, state, applyMiddleware(thunkMiddleware));

為了下文好理解這個(gè)放一下redux-thunk的源碼

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;

可以看出thunk返回了一個(gè)接受({ dispatch, getState })為參數(shù)的函數(shù)
下面我們來看一下applyMiddleware源碼分析

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 每個(gè) middleware 都以 middlewareAPI 作為參數(shù)進(jìn)行注入溃列,返回一個(gè)新的鏈劲厌。此時(shí)的返回值相當(dāng)于調(diào)用 thunkMiddleware 返回的函數(shù): (next) => (action) => {} ,接收一個(gè)next作為其參數(shù)
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 并將鏈代入進(jìn) compose 組成一個(gè)函數(shù)的調(diào)用鏈
    // compose(...chain) 返回形如(...args) => f(g(h(...args)))听隐,f/g/h都是chain中的函數(shù)對象补鼻。
    // 在目前只有 thunkMiddleware 作為 middlewares 參數(shù)的情況下,將返回 (next) => (action) => {}
    // 之后以 store.dispatch 作為參數(shù)進(jìn)行注入注意這里這里的store.dispatch是沒有被修改的dispatch他被傳給了next;
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

// 定義一個(gè)代碼組合的方法
// 傳入一些function作為參數(shù)风范,返回其鏈?zhǔn)秸{(diào)用的形態(tài)咨跌。例如,
// compose(f, g, h) 最終返回 (...args) => f(g(h(...args)))
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  } else {
    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  }
}

也就是一個(gè)三級柯里化的函數(shù)硼婿,我們從頭來分析一下這個(gè)過程

// createStore.js
if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }
  return enhancer(createStore)(reducer, preloadedState)
}

也就是說锌半,會變成這樣

applyMiddleware(thunkMiddleware)(createStore)(reducer, preloadedState)
  • applyMiddleware(thunkMiddleware)
    applyMiddleware接收thunkMiddleware作為參數(shù),返回形如(createStore) => (...args) => {}的函數(shù)寇漫。
  • applyMiddleware(thunkMiddleware)(createStore)
    以 createStore 作為參數(shù)刊殉,調(diào)用上一步返回的函數(shù)(...args) => {}
  • applyMiddleware(thunkMiddleware)(createStore)(reducer, preloadedState)
    以(reducer, preloadedState)為參數(shù)進(jìn)行調(diào)用。 在這個(gè)函數(shù)內(nèi)部州胳,thunkMiddleware被調(diào)用冗澈,其作用是監(jiān)測type是function的action。
    因此陋葡,如果dispatch的action返回的是一個(gè)function亚亲,則證明是中間件,則將(dispatch, getState)作為參數(shù)代入其中腐缤,進(jìn)行action 內(nèi)部下一步的操作捌归。否則的話,認(rèn)為只是一個(gè)普通的action岭粤,將通過next(也就是dispatch)進(jìn)一步分發(fā)惜索。

也就是說,applyMiddleware(thunkMiddleware)作為enhance剃浇,最終起了這樣的作用:

對dispatch調(diào)用的action進(jìn)行檢查巾兆,如果action在第一次調(diào)用之后返回的是function,則將(dispatch, getState)作為參數(shù)注入到action返回的方法中虎囚,否則就正常對action進(jìn)行分發(fā)角塑,這樣一來我們的中間件就完成了。
因此淘讥,當(dāng)action內(nèi)部需要獲取state圃伶,或者需要進(jìn)行異步操作,在操作完成之后進(jìn)行事件調(diào)用分發(fā)的話蒲列,我們就可以讓action 返回一個(gè)以(dispatch, getState)為參數(shù)的function而不是通常的Object窒朋,enhance就會對其進(jìn)行檢測以便正確的處理。

到此redux源碼的主要部分學(xué)習(xí)結(jié)束蝗岖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侥猩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子抵赢,更是在濱河造成了極大的恐慌欺劳,老刑警劉巖洛退,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異杰标,居然都是意外死亡兵怯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門腔剂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來媒区,“玉大人,你說我怎么就攤上這事掸犬⊥噤觯” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵湾碎,是天一觀的道長宙攻。 經(jīng)常有香客問我,道長介褥,這世上最難降的妖魔是什么座掘? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮柔滔,結(jié)果婚禮上溢陪,老公的妹妹穿的比我還像新娘。我一直安慰自己睛廊,他們只是感情好形真,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著超全,像睡著了一般咆霜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嘶朱,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天蛾坯,我揣著相機(jī)與錄音,去河邊找鬼见咒。 笑死偿衰,一個(gè)胖子當(dāng)著我的面吹牛挂疆,可吹牛的內(nèi)容都是我干的改览。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼缤言,長吁一口氣:“原來是場噩夢啊……” “哼宝当!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胆萧,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤庆揩,失蹤者是張志新(化名)和其女友劉穎俐东,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體订晌,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虏辫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锈拨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俏脊。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柒昏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毕骡,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布缰趋,位于F島的核電站总放,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谷浅。R本人自食惡果不足惜扒俯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望一疯。 院中可真熱鬧陵珍,春花似錦、人聲如沸违施。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽磕蒲。三九已至留潦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辣往,已是汗流浹背兔院。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留站削,地道東北人坊萝。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像许起,于是被迫代替她去往敵國和親十偶。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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