Redux源碼解析(學(xué)習(xí)筆記)

最近比較空閑脚乡,就想看點(diǎn)源碼蜒滩,學(xué)習(xí)記錄一下。如果自己收獲能給他人些幫助奶稠,是一件開心的事也會更有動力俯艰。今天的主角是Redux。
先嘮叨幾句耳熟能詳?shù)脑?/p>

Redux 是 JavaScript 狀態(tài)容器锌订,提供可預(yù)測化的狀態(tài)管理竹握。

Redux三大原則:

  • 單一數(shù)據(jù)源
  • State 只能通過觸發(fā)Action
  • 使用純函數(shù)來執(zhí)行修改

Redux的源碼很少,5個核心文件辆飘。

QQ截圖20201010152216.png

1. 首先來看createStore.js啦辐,一切的開始。

const store = createStore(reducer, preloadedState, enhancer);

這個函數(shù)默認(rèn)接收三個參數(shù)(改變狀態(tài)的方法蜈项,狀態(tài)的初始值芹关,增強(qiáng)器(增強(qiáng)后的store)),返回的store對象中包含dispatch紧卒,getState侥衬,subscribe等方法。(筆者只列出在工作中常用的)跑芳。
這個時候轴总,代碼應(yīng)該是這樣的

function createStore(reducer, preloadedState, enhancer) {
  function dispatch() {}
  function getState() {}
  function subscribe() {}
  return {
    dispatch,
    getState,
    subscribe
  };
}

接下來,就要對傳入?yún)?shù)做判斷博个,按規(guī)矩辦事怀樟。這里就直接貼源碼了,都是條件判斷坡倔。


image.png

重點(diǎn)注意下漂佩,紅框中的部分。至于這里為什么這么寫罪塔,后面細(xì)說投蝉。不耽誤理解createStore這個函數(shù)。
下面將這個三個函數(shù)分別簡單實(shí)現(xiàn)

  1. getState 從簡單入手,將初始的state或者改變后的state返回征堪。代碼變成這樣
function createStore(reducer, preloadedState, enhancer) {
  let currentState = preloadedState;
  function getState() {
      return currentState;
  }
  return { getState };
}
  1. subscribe 使用發(fā)布訂閱模式瘩缆,收集每一個訂閱state的函數(shù)。
function createStore(reducer, preloadedState, enhancer) {
  let currentListeners = []
  let nextListeners = currentListeners
  function subscribe(listener) {
    //  ensureCanMutateNextListeners() 這個函數(shù)在官方的解釋為佃蚜,進(jìn)行淺拷貝庸娱,防止分發(fā)過程中着绊,增加/取消訂閱事件,而引發(fā)出問題熟尉。先去掉
    nextListeners.push(listener)
    return function unsubscribe() {
    // nextListeners.splice(index, 1) 
   }
  }
  return { subscribe };
  1. dispatch 在dispatch的時候归露,會改變state,reducer返回新的state斤儿,所以dispatch內(nèi)部調(diào)用reducer剧包。訂閱者能收到消息,說明subscribe訂閱的函數(shù)會執(zhí)行往果。
function createStore(reducer, preloadedState, enhancer) {
  let currentState = preloadedState;
  let currentReducer = reducer;
  let nextListeners = currentListeners

  function dispatch(action) {
    currentState = currentReducer(action);
    for (let i = 0; i < nextListeners .length; i++) {
      const listener = listeners[i]
      listener()
    }
    return action;
  }
// 默認(rèn)會執(zhí)行一次 dispatch,簡單寫
// dispatch();
  return {
    getState,
    dispatch,
    subscribe
  };
}

不傳入增強(qiáng)器enhancer時疆液,代碼大致是這樣

function createStore(reducer, preloadedState, enhancer) {
  let currentState = preloadedState;
  let currentReducer = reducer;
  let currentListeners = []
  let nextListeners = currentListeners
  function getState() {
      return currentState;
  }
  function dispatch(action) {
    currentState = currentReducer(action);
    for (let i = 0; i < nextListeners .length; i++) {
      const listener = listeners[i]
      listener();
    }
    return action;
  }

  function subscribe(listener) {
    // ensureCanMutateNextListeners();
    nextListeners.push(listener)
    return function unsubscribe() {}
  }
  return { getState, dispatch, subscribe };
}

現(xiàn)在陕贮,我們來看傳入enhancer的情況堕油。這里要用到applyMiddleware.js中的applyMiddleware函數(shù),這個函數(shù)返回增強(qiáng)后的store肮之,將dispatch方法增強(qiáng)掉缺。讓原來一步操作的dispatch(action),變成執(zhí)行func1->func2->...->store.dispatch(action)局骤。

// 上面createStore.js  -> enhancer(createStore)(reducer, preloadedState)  => { getState, dispatch, subscribe } 這個是調(diào)用applyMiddleware()函數(shù)后的返回函數(shù)enhancer攀圈,enhancer執(zhí)行2次最后返回對象store,由此推測代碼應(yīng)該是個樣子的
function applyMiddleware(...middlewares) {
  return () => () => ({ getState, dispatch, subscribe })
}

第一步要有store峦甩,才能增強(qiáng)其dispatch方法赘来,獲取store的唯一途徑是調(diào)用createStore函數(shù),將reducer和initialState傳入】粒現(xiàn)在改成這樣

// applyMiddleware(...middlewares) -> enhancer(createStore)(reducer, preloadedState)
// enhancer(createStore) ->  (...args) => { const store = createStore(...args);  return store; }
// enhancer(createStore)(reducer, preloadedState) => store;
function applyMiddleware(...middlewares) {
  return (createStore) => (...args) => {
     const store = createStore(...args);
     return store;
  }
}

下面犬辰,把中間件引進(jìn)來,我們知道中間件是層層包裹的冰单,經(jīng)過中間件后幌缝,返回的dispatch再調(diào)用dispatch時,會把外層函數(shù)都執(zhí)行了诫欠,最后再執(zhí)行dispatch(action)涵卵。可能是這樣

 function applyMiddleware(...middlewares) {
    return createStore => (...args) => {
      const store = createStore(...args);
      let dispatch = compose(...middlewares)(store.dispatch);
      return { ...store, dispatch };
    }
 }

compose.js ,返回一個函數(shù)

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)));
}

個人猜測這里中間件如果這樣寫compose(...middlewares)(store.dispatch)荒叼,中間件的只能操作dispatch轿偎,將這個store整體傳入又不安全。如果給每個中間件都注入一個getState被廓,dispatch坏晦。就能做更多的事情。代碼可能變成這樣

 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,
          // 在redux 3.x的版本源碼,都是這樣的 dispatch: (...args) => (store.dispatch)(...args);  這好理解每個中間件通過作用域鏈拿到dispatch昆婿,接下來就按照3.x的說球碉。
          // 4.x版本的寫法,自己沒想明白仓蛆,上面的英文解釋說睁冬,防止在構(gòu)建時調(diào)用dispatch,我自己還沒有領(lǐng)會到精髓多律,了解的朋友請幫忙指點(diǎn)迷津 
          dispatch: (...args) => dispatch(...args)
      }
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      let dispatch = compose(...chain)(store.dispatch);
      return { ...store, dispatch };
    }
 }

這樣痴突,就可以在每次調(diào)用dispatch時,由中間件控制dispatch何時調(diào)用狼荞。
最后,再來看一下中間件的寫法帮碰。({getState, dispatch}) => next => action => {},格式固定相味。
以redux-thunk為例,簡單說一下殉挽。

// 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();  // 這里執(zhí)行了一次
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;

最簡單的使用方式,當(dāng)參數(shù)是函數(shù)時丰涉,就執(zhí)行這個函數(shù)并將dispatch、getState作為參數(shù)傳入斯碌。

store.dispatch(
  dispatch => {
    setTimeout( () => {
      dispatch(action)
    }一死,1000)
  }
);

第一次寫博客,有點(diǎn)慌張語言的組織不太好傻唾,有些地方還有點(diǎn)啰嗦投慈。如果個人理解偏差,請大家不吝賜教冠骄,共同進(jìn)步伪煤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市凛辣,隨后出現(xiàn)的幾起案子抱既,更是在濱河造成了極大的恐慌,老刑警劉巖扁誓,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件防泵,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門氢哮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剩檀,“玉大人,你說我怎么就攤上這事谬运∫腋鳎” “怎么了爬骤?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵骡湖,是天一觀的道長贱纠。 經(jīng)常有香客問我,道長响蕴,這世上最難降的妖魔是什么谆焊? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮浦夷,結(jié)果婚禮上辖试,老公的妹妹穿的比我還像新娘。我一直安慰自己劈狐,他們只是感情好罐孝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肥缔,像睡著了一般莲兢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上续膳,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天改艇,我揣著相機(jī)與錄音,去河邊找鬼坟岔。 笑死谒兄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的社付。 我是一名探鬼主播承疲,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瘦穆!你這毒婦竟也來了纪隙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤扛或,失蹤者是張志新(化名)和其女友劉穎绵咱,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體熙兔,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悲伶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了住涉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麸锉。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖舆声,靈堂內(nèi)的尸體忽然破棺而出花沉,到底是詐尸還是另有隱情柳爽,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布碱屁,位于F島的核電站磷脯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏娩脾。R本人自食惡果不足惜赵誓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柿赊。 院中可真熱鬧俩功,春花似錦、人聲如沸碰声。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奥邮。三九已至万牺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洽腺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工覆旱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蘸朋,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓扣唱,卻偏偏與公主長得像藕坯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子噪沙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353