學習 redux 源碼總體架構,深刻理解 redux 及其中間件原理

1. 前言

你好,我是若川。這是學習源碼總體架構系列第八篇。總體架構這詞語好像有點大,姑且就算是源碼總體結構吧,主要就是學習是代碼總體結構,不深究其餘不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。html

要是有人說到怎麼讀源碼,正在讀文章的你能推薦個人源碼系列文章,那真是太好了。前端

學習源碼總體架構系列文章以下:vue

1.學習 jQuery 源碼總體架構,打造屬於本身的 js 類庫
2.學習 underscore 源碼總體架構,打造屬於本身的函數式編程類庫
3.學習 lodash 源碼總體架構,打造屬於本身的函數式編程類庫
4.學習 sentry 源碼總體架構,打造屬於本身的前端異常監控SDK
5.學習 vuex 源碼總體架構,打造屬於本身的狀態管理庫
6.學習 axios 源碼總體架構,打造屬於本身的請求庫
7.學習 koa 源碼的總體架構,淺析koa洋蔥模型原理和co原理
8.學習 redux 源碼總體架構,深刻理解 redux 及其中間件原理
react

感興趣的讀者能夠點擊閱讀。
其餘源碼計劃中的有:expressvue-rotuerreact-redux 等源碼,不知什麼時候能寫完(哭泣),歡迎持續關注我(若川)。ios

源碼類文章,通常閱讀量不高。已經有能力看懂的,本身就看了。不想看,不敢看的就不會去看源碼。
因此個人文章,儘可能寫得讓想看源碼又不知道怎麼看的讀者能看懂。git

閱讀本文你將學到:github

  1. git subtree 管理子倉庫
  2. 如何學習 redux 源碼
  3. redux 中間件原理
  4. redux 各個API的實現
  5. vuexredux 的對比
  6. 等等

1.1 本文閱讀最佳方式

把個人redux源碼倉庫 git clone https://github.com/lxchuan12/redux-analysis.git克隆下來,順便star一下個人redux源碼學習倉庫^_^。跟着文章節奏調試和示例代碼調試,用chrome動手調試印象更加深入。文章長段代碼不用細看,能夠調試時再細看。看這類源碼文章百遍,可能不如本身多調試幾遍。也歡迎加我微信交流ruochuan12面試

2. git subtree 管理子倉庫

寫了不少源碼文章,vuexaxioskoa等都是使用新的倉庫克隆一份源碼在本身倉庫中。 雖然電腦能夠拉取最新代碼,看到原做者的git信息。但上傳到github後。讀者卻看不到原倉庫做者的git信息了。因而我找到了git submodules 方案,但並非很適合。再後來發現了git subtreevue-router

簡單說下 npm packagegit subtree的區別。 npm package是單向的。git subtree則是雙向的。vuex

具體能夠查看這篇文章@德來(原有贊大佬):用 Git Subtree 在多個 Git 項目間雙向同步子項目,附簡明使用手冊

學會了git subtree後,我新建了redux-analysis項目後,把redux源碼4.x(截止至2020年06月13日,4.x分支最新版本是4.0.5master分支是ts,文章中暫不想讓一些不熟悉ts的讀者看不懂)分支克隆到了個人項目裏的一個子項目,得以保留git信息。

對應命令則是:

git subtree add --prefix=redux https://github.com/reduxjs/redux.git 4.x
複製代碼

3. 調試 redux 源碼準備工做

以前,我在知乎回答了一個問題若川:一年內的前端看不懂前端框架源碼怎麼辦? 推薦了一些資料,閱讀量還不錯,你們有興趣能夠看看。主要有四點:

1.藉助調試
2.搜索查閱相關高贊文章
3.把不懂的地方記錄下來,查閱相關文檔
4.總結

看源碼調試很重要,因此個人每篇源碼文章都詳細描述(也許有人看來是比較囉嗦...)如何調試源碼。

斷點調試要領:
賦值語句能夠一步按F10跳過,看返回值便可,後續詳細再看。
函數執行須要斷點按F11跟着看,也能夠結合註釋和上下文倒推這個函數作了什麼。
有些不須要細看的,直接按F8走向下一個斷點
刷新從新調試按F5

調試源碼前,先簡單看看 redux 的工做流程,有個大概印象。

redux 工做流程
redux 工做流程

3.1 rollup 生成 sourcemap 便於調試

修改rollup.config.js文件,output輸出的配置生成sourcemap

// redux/rollup.config.js 有些省略
const sourcemap = {  sourcemap: true, };  output: {  // ...  ...sourcemap, } 複製代碼

安裝依賴

git clone http://github.com/lxchuan12/redux-analysis.git
cd redux-analysi/redux npm i npm run build # 編譯結束後會生成 sourcemap .map格式的文件到 dist、es、lib 目錄下。 複製代碼

仔細看看redux/examples目錄和redux/README

這時我在根路徑下,新建文件夾examples,把原生js寫的計數器redux/examples/counter-vanilla/index.html,複製到examples/index.html。同時把打包後的包含sourcemapredux/dist目錄,複製到examples/dist目錄。

修改index.htmlscriptredux.js文件爲dist中的路徑

爲了便於區分和調試後續html文件,我把index.html重命名爲index.1.redux.getState.dispatch.html

# redux-analysis 根目錄
# 安裝啓動服務的npm包 npm i -g http-server cd examples hs -p 5000 複製代碼

就能夠開心的調試啦。能夠直接克隆個人項目git clone http://github.com/lxchuan12/redux-analysis.git。本地調試,動手實踐,容易消化吸取。

4. 經過調試計數器例子的學習 redux 源碼

接着咱們來看examples/index.1.redux.getState.dispatch.html文件。先看html部分。只是寫了幾個 button,比較簡單。

<div>
 <p>  Clicked: <span id="value">0</span> times  <button id="increment">+</button>  <button id="decrement">-</button>  <button id="incrementIfOdd">Increment if odd</button>  <button id="incrementAsync">Increment async</button>  </p> </div> 複製代碼

js部分,也比較簡單。聲明瞭一個counter函數,傳遞給Redux.createStore(counter),獲得結果store,而store是個對象。render方法渲染數字到頁面。用store.subscribe(render)訂閱的render方法。還有store.dispatch({type: 'INCREMENT' })方法,調用store.dispatch時會觸發render方法。這樣就實現了一個計數器。

function counter(state, action) {
 if (typeof state === 'undefined') {  return 0  }   switch (action.type) {  case 'INCREMENT':  return state + 1  case 'DECREMENT':  return state - 1  default:  return state  } }  var store = Redux.createStore(counter) var valueEl = document.getElementById('value')  function render() {  valueEl.innerHTML = store.getState().toString() } render() store.subscribe(render)  document.getElementById('increment') .addEventListener('click', function () {  store.dispatch({ type: 'INCREMENT' }) })  // 省略部分暫時無效代碼... 複製代碼

思考:看了這段代碼,你會在哪打斷點來調試呢。

// 四處能夠斷點來看
// 1. var store = Redux.createStore(counter) // 2. function render() { valueEl.innerHTML = store.getState().toString() } render() // 3. store.subscribe(render) // 4. store.dispatch({ type: 'INCREMENT' }) 複製代碼
redux debugger圖
redux debugger圖

圖中的右邊Scope,有時須要關注下,會顯示閉包、全局環境、當前環境等變量,還能夠顯示函數等具體代碼位置,能幫助本身理解代碼。

斷點調試,按F5刷新頁面後,按F8,把鼠標放在Reduxstore上。

能夠看到Redux上有好幾個方法。分別是:

  • __DO_NOT_USE__ActionTypes: {INIT: "@@redux/INITu.v.d.u.6.r", REPLACE: "@@redux/REPLACEg.u.u.7.c", PROBE_UNKNOWN_ACTION: ƒ}
  • applyMiddleware: ƒ applyMiddleware() 函數是一個加強器,組合多箇中間件,最終加強store.dispatch函數,dispatch時,能夠串聯執行全部中間件。
  • bindActionCreators: ƒ bindActionCreators(actionCreators, dispatch) 生成actions,主要用於其餘庫,好比react-redux
  • combineReducers: ƒ combineReducers(reducers) 組合多個reducers,返回一個總的reducer函數。
  • compose: ƒ compose() 組合多個函數,從右到左,好比:compose(f, g, h) 最終獲得這個結果 (...args) => f(g(h(...args))).
  • createStore: ƒ createStore(reducer, preloadedState, enhancer) 生成 store 對象

再看store也有幾個方法。分別是:

  • dispatch: ƒ dispatch(action) 派發動做,也就是把subscribe收集的函數,依次遍歷執行
  • subscribe: ƒ subscribe(listener) 訂閱收集函數存在數組中,等待觸發dispatch依次執行。返回一個取消訂閱的函數,能夠取消訂閱監聽。
  • getState: ƒ getState() 獲取存在createStore函數內部閉包的對象。
  • replaceReducer: ƒ replaceReducer(nextReducer) 主要用於redux開發者工具,對比當前和上一次操做的異同。有點相似時間穿梭功能。
  • Symbol(observable): ƒ observable()

也就是官方文檔redux.org.js上的 API

暫時不去深究每個API的實現。從新按F5刷新頁面,斷點到var store = Redux.createStore(counter)。一直按F11,先走一遍主流程。

4.1 Redux.createSotre

createStore 函數結構是這樣的,是否是看起來很簡單,最終返回對象store,包含dispatchsubscribegetStatereplaceReducer等方法。

// 省略了若干代碼
export default function createStore(reducer, preloadedState, enhancer) {  // 省略參數校驗和替換  // 當前的 reducer 函數  let currentReducer = reducer  // 當前state  let currentState = preloadedState  // 當前的監聽數組函數  let currentListeners = []  // 下一個監聽數組函數  let nextListeners = currentListeners  // 是否正在dispatch中  let isDispatching = false  function ensureCanMutateNextListeners() {  if (nextListeners === currentListeners) {  nextListeners = currentListeners.slice()  }  }  function getState() {  return currentState  }  function subscribe(listener) {}  function dispatch(action) {}  function replaceReducer(nextReducer) {}  function observable() {}  // ActionTypes.INIT @@redux/INITu.v.d.u.6.r  dispatch({ type: ActionTypes.INIT })  return {  dispatch,  subscribe,  getState,  replaceReducer,  [$observable]: observable  } } 複製代碼

4.2 store.dispatch(action)

function dispatch(action) {
 // 判斷action是不是對象,不是則報錯  if (!isPlainObject(action)) {  throw new Error(  'Actions must be plain objects. ' +  'Use custom middleware for async actions.'  )  }  // 判斷action.type 是否存在,沒有則報錯  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 {  // 調用完後置爲 false  isDispatching = false  }  // 把 收集的函數拿出來依次調用  const listeners = (currentListeners = nextListeners)  for (let i = 0; i < listeners.length; i++) {  const listener = listeners[i]  listener()  }  // 最終返回 action  return action  } 複製代碼
var store = Redux.createStore(counter)
複製代碼

上文調試完了這句。

繼續按F11調試。

function render() {
 valueEl.innerHTML = store.getState().toString() } render() 複製代碼

4.3 store.getState()

getState函數實現比較簡單。

function getState() {
 // 判斷正在dispatch中,則報錯  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.'  )  }  // 返回當前的state  return currentState } 複製代碼

4.4 store.subscribe(listener)

訂閱監聽函數,存放在數組中,store.dispatch(action)時遍歷執行。

function subscribe(listener) {
 // 訂閱參數校驗不是函數報錯  if (typeof listener !== 'function') {  throw new Error('Expected the listener to be a function.')  }  // 正在dispatch中,報錯  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#subscribelistener for more details.'  )  }  // 訂閱爲 true  let isSubscribed = true   ensureCanMutateNextListeners()  nextListeners.push(listener)   // 返回一個取消訂閱的函數  return function unsubscribe() {  if (!isSubscribed) {  return  }  // 正在dispatch中,則報錯  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#subscribelistener for more details.'  )  }  // 訂閱爲 false  isSubscribed = false   ensureCanMutateNextListeners()  // 找到當前監聽函數  const index = nextListeners.indexOf(listener)  // 在數組中刪除  nextListeners.splice(index, 1)  currentListeners = null  }  } 複製代碼

到這裏,咱們就調試學習完了Redux.createSotrestore.dispatchstore.getStatestore.subscribe的源碼。

接下來,咱們寫個中間件例子,來調試中間件相關源碼。

5. Redux 中間件相關源碼

中間件是重點,面試官也常常問這類問題。

5.1 Redux.applyMiddleware(...middlewares)

5.1.1 準備 logger 例子調試

爲了調試Redux.applyMiddleware(...middlewares),我在examples/js/middlewares.logger.example.js寫一個簡單的logger例子。分別有三個logger1logger2logger3函數。因爲都是相似,因此我在這裏只展現logger1函數。

// examples/js/middlewares.logger.example.js
function logger1({ getState }) {  return next => action => {  console.log('will dispatch--1--next, action:', next, action)   // Call the next dispatch method in the middleware chain.  const returnValue = next(action)   console.log('state after dispatch--1', getState())   // This will likely be the action itself, unless  // a middleware further in chain changed it.  return returnValue  } } // 省略 logger二、logger3 複製代碼

logger中間件函數作的事情也比較簡單,返回兩層函數,next就是下一個中間件函數,調用返回結果。爲了讓讀者能看懂,我把logger1用箭頭函數、logger2則用普通函數。

寫好例子後,咱們接着來看怎麼調試Redux.applyMiddleware(...middlewares))源碼。

cd redux-analysis && hs -p 5000
# 上文說過npm i -g http-server 複製代碼

打開http://localhost:5000/examples/index.2.redux.applyMiddleware.compose.html,按F12打開控制檯,

先點擊加號操做+1,把結果展現出來。

redux 中間件調試圖
redux 中間件調試圖

從圖中能夠看出,next則是下一個函數。先1-2-3,再3-2-1這樣的順序。

這種也就是咱們常說的中間件,面向切面編程(AOP)。

中間件圖解
中間件圖解

接下來調試,在如下語句打上斷點和一些你以爲重要的地方打上斷點。

// examples/index.2.redux.applyMiddleware.compose.html
var store = Redux.createStore(counter, Redux.applyMiddleware(logger1, logger2, logger3)) 複製代碼

5.1.2 Redux.applyMiddleware(...middlewares) 源碼

// redux/src/applyMiddleware.js
/**  * ...  * @param {...Function} middlewares The middleware chain to be applied.  * @returns {Function} A store enhancer applying the middleware.  */ 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)  }  const chain = middlewares.map(middleware => middleware(middlewareAPI))  dispatch = compose(...chain)(store.dispatch)   return {  ...store,  dispatch  }  } } 複製代碼
// redux/src/createStore.js
export default function createStore(reducer, preloadedState, enhancer) {  // 省略參數校驗  // 若是第二個參數`preloadedState`是函數,而且第三個參數`enhancer`是undefined,把它們互換一下。  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.')  }  // enhancer 也就是`Redux.applyMiddleware`返回的函數  // createStore 的 args 則是 `reducer, preloadedState`  /**  * createStore => (...args) => {  const store = createStore(...args)  return {  ...store,  dispatch,  }  }  ** /  // 最終返回加強的store對象。  return enhancer(createStore)(reducer, preloadedState)  }  // 省略後續代碼 } 複製代碼

把接收的中間件函數logger1, logger2, logger3放入到 了middlewares數組中。Redux.applyMiddleware最後返回兩層函數。 把中間件函數都混入了參數getStatedispatch

// examples/index.2.redux.applyMiddleware.compose.html
var store = Redux.createStore(counter, Redux.applyMiddleware(logger1, logger2, logger3)) 複製代碼

最後這句實際上是返回一個加強了dispatchstore對象。

而加強的dispatch函數,則是用Redux.compose(...functions)進行串聯起來執行的。

5.2 Redux.compose(...functions)

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))) } 複製代碼
// applyMiddleware.js
dispatch = compose(...chain)(store.dispatch) // compose funcs.reduce((a, b) => (...args) => a(b(...args))) 複製代碼

這兩句可能不是那麼好理解,能夠斷點多調試幾回。我把箭頭函數轉換成普通函數。

funcs.reduce(function(a, b){
 return function(...args){  return a(b(...args));  }; }); 複製代碼

其實redux源碼中註釋很清晰了,這個compose函數上方有一堆註釋,其中有一句:組合多個函數,從右到左,好比:compose(f, g, h) 最終獲得這個結果 (...args) => f(g(h(...args))).

5.2.1 compose 函數演化

Redux.compose(...functions)函數源碼後,仍是不明白,不要急不要慌,吃完雞蛋還有湯。仔細來看如何演化而來,先來簡單看下以下需求。

傳入一個數值,計算數值乘以10再加上10,再減去2。

實現起來很簡單。

const calc = (num) => num * 10 + 10 - 2;
calc(10); // 108 複製代碼

但這樣寫有個問題,很差擴展,好比我想乘以10時就打印出結果。 爲了便於擴展,咱們分開寫成三個函數。

const multiply = (x) => {
 const result = x * 10;  console.log(result);  return result; }; const add = (y) => y + 10; const minus = (z) => z - 2;  // 計算結果 console.log(minus(add(multiply(10)))); // 100 // 108 // 這樣咱們就把三個函數計算結果出來了。 複製代碼

再來實現一個相對通用的函數,計算這三個函數的結果。

const compose = (f, g, h) => {
 return function(x){  return f(g(h(x)));  } } const calc = compose(minus, add, multiply); console.log(calc(10)); // 100 // 108 複製代碼

這樣仍是有問題,只支持三個函數。我想支持多個函數。 咱們瞭解到數組的reduce方法就能實現這樣的功能。 前一個函數

// 咱們經常使用reduce來計算數值數組的總和
[1,2,3,4,5].reduce((pre, item, index, arr) => {  console.log('(pre, item, index, arr)', pre, item, index, arr);  // (pre, item, index, arr) 1 2 1 (5) [1, 2, 3, 4, 5]  // (pre, item, index, arr) 3 3 2 (5) [1, 2, 3, 4, 5]  // (pre, item, index, arr) 6 4 3 (5) [1, 2, 3, 4, 5]  // (pre, item, index, arr) 10 5 4 (5) [1, 2, 3, 4, 5]  return pre + item; }); // 15 複製代碼

pre 是上一次返回值,在這裏是數值1,3,6,10。在下一個例子中則是匿名函數。

function(x){
 return a(b(x)); } 複製代碼

item2,3,4,5,在下一個例子中是minus、add、multiply

const compose = (...funcs) => {
 return funcs.reduce((a, b) => {  return function(x){  return a(b(x));  }  }) } const calc = compose(minus, add, multiply); console.log(calc(10)); // 100 // 108 複製代碼

Redux.compose(...functions)其實就是這樣,只不過中間件是返回雙層函數罷了。

因此返回的是next函數,他們串起來執行了,造成了中間件的洋蔥模型。 人們都說一圖勝千言。我畫了一個相對簡單的redux中間件原理圖。

中間件原理圖
redux中間件原理圖

若是還不是很明白,建議按照我給出的例子,多調試。

cd redux-analysis && hs -p 5000
# 上文說過npm i -g http-server 複製代碼

打開http://localhost:5000/examples/index.3.html,按F12打開控制檯調試。

5.2.2 前端框架的 compose 函數的實現

lodash源碼中 compose函數的實現,也是相似於數組的reduce,只不過是內部實現的arrayReduce

引用自個人文章:學習lodash源碼總體架構

// lodash源碼
function baseWrapperValue(value, actions) {  var result = value;  // 若是是lazyWrapper的實例,則調用LazyWrapper.prototype.value 方法,也就是 lazyValue 方法  if (result instanceof LazyWrapper) {  result = result.value();  }  // 相似 [].reduce(),把上一個函數返回結果做爲參數傳遞給下一個函數  return arrayReduce(actions, function(result, action) {  return action.func.apply(action.thisArg, arrayPush([result], action.args));  }, result); } 複製代碼

koa-compose源碼也有compose函數的實現。實現是循環加promise。 因爲代碼比較長我就省略了,具體看連接若川:學習 koa 源碼的總體架構,淺析koa洋蔥模型原理和co原理小節 koa-compose 源碼(洋蔥模型實現)

6. Redux.combineReducers(reducers)

打開http://localhost:5000/examples/index.4.html,按F12打開控制檯,按照給出的例子,調試接下來的Redux.combineReducers(reducers)Redux.bindActionCreators(actionCreators, dispatch)具體實現。因爲文章已經很長了,這兩個函數就不那麼詳細解釋了。

combineReducers函數簡單來講就是合併多個reducer爲一個函數combination

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 (typeof reducers[key] === 'function') {  finalReducers[key] = reducers[key]  }  }   // 通過一些處理後獲得最後的finalReducerKeys  const finalReducerKeys = Object.keys(finalReducers)   // 省略一些開發環境判斷的代碼...   return function combination(state = {}, action) {  // ... 省略開發環境的一些判斷   // 用 hasChanged變量 記錄先後 state 是否已經修改  let hasChanged = false  // 聲明對象來存儲下一次的state  const nextState = {}  //遍歷 finalReducerKeys  for (let i = 0; i < finalReducerKeys.length; i++) {  const key = finalReducerKeys[i]  const reducer = finalReducers[key]  const previousStateForKey = state[key]  // 執行 reducer  const nextStateForKey = reducer(previousStateForKey, action)   // 省略容錯代碼 ...   nextState[key] = nextStateForKey  // 兩次 key 對比 不相等則發生改變  hasChanged = hasChanged || nextStateForKey !== previousStateForKey  }  // 最後的 keys 數組對比 不相等則發生改變  hasChanged =  hasChanged || finalReducerKeys.length !== Object.keys(state).length  return hasChanged ? nextState : state  } } 複製代碼

7. Redux.bindActionCreators(actionCreators, dispatch)

若是第一個參數是一個函數,那就直接返回一個函數。若是是一個對象,則遍歷賦值,最終生成boundActionCreators對象。

function bindActionCreator(actionCreator, dispatch) {
 return function() {  return dispatch(actionCreator.apply(this, arguments))  } }  export default function bindActionCreators(actionCreators, dispatch) {  if (typeof actionCreators === 'function') {  return bindActionCreator(actionCreators, dispatch)  }   // ... 省略一些容錯判斷   const boundActionCreators = {}  for (const key in actionCreators) {  const actionCreator = actionCreators[key]  if (typeof actionCreator === 'function') {  boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)  }  }  return boundActionCreators } 複製代碼

redux所提供的的API 除了store.replaceReducer(nextReducer)沒分析,其餘都分析了。

8. vuex 和 redux 簡單對比

8.1 源碼實現形式

從源碼實現上來看,vuex源碼主要使用了構造函數,而redux則是多用函數式編程、閉包。

8.2 耦合度

vuexvue 強耦合,脫離了vue則沒法使用。而reduxreact沒有關係,因此它可使用於小程序或者jQuery等。若是須要和react使用,還須要結合react-redux庫。

8.3 擴展

// logger 插件,具體實現省略
function logger (store) {  console.log('store', store); } // 做爲數組傳入 new Vuex.Store({  state,  getters,  actions,  mutations,  plugins: process.env.NODE_ENV !== 'production'  ? [logger]  : [] }) // vuex 源碼 插件執行部分 class Store{  constructor(){  // 把vuex的實例對象 store整個對象傳遞給插件使用  plugins.forEach(plugin => plugin(this))  } } 複製代碼

vuex實現擴展則是使用插件形式,而redux是中間件的形式。redux的中間件則是AOP(面向切面編程),reduxRedux.applyMiddleware()其實也是一個加強函數,因此也能夠用戶來實現加強器,因此redux生態比較繁榮。

8.4 上手難易度

相對來講,vuex上手相對簡單,redux相對難一些,redux涉及到一些函數式編程、高階函數、純函數等概念。

9. 總結

文章主要經過一步步調試的方式按部就班地講述redux源碼的具體實現。旨在教會讀者調試源碼,不害怕源碼。

面試官常常喜歡考寫一個redux中間件,說說redux中間件的原理。

function logger1({ getState }) {
 return next => action => {  const returnValue = next(action)  return returnValue  } } 複製代碼
const 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)))  return funcs.reduce((a, b) => {  return function(x){  return a(b(x));  }  }) } 複製代碼
const enhancerStore = Redux.create(reducer, Redux.applyMiddleware(logger1, ...))
enhancerStore.dispatch(action) 複製代碼

用戶觸發enhancerStore.dispatch(action)是加強後的,其實就是第一個中間件函數,中間的next是下一個中間件函數,最後next是沒有加強的store.dispatch(action)

最後再來看張redux工做流程圖 工做流程圖

是否是就更理解些了呢。

若是讀者發現有不妥或可改善之處,再或者哪裏沒寫明白的地方,歡迎評論指出。另外以爲寫得不錯,對你有些許幫助,能夠點贊、評論、轉發分享,也是對個人一種支持,很是感謝呀。要是有人說到怎麼讀源碼,正在讀文章的你能推薦個人源碼系列文章,那真是太好了

推薦閱讀

@鬍子大哈:動手實現 Redux(一):優雅地修改共享狀態,總共6小節,很是推薦,雖然我很早前就看完了《react小書》,如今再看一遍又有收穫
美團@瑩瑩 Redux從設計到源碼,美團這篇是我基本寫完文章後看到的,感受寫得很好,很是推薦
redux 中文文檔
redux 英文文檔
若川的學習redux源碼倉庫

另外一個系列

面試官問:JS的繼承
面試官問:JS的this指向
面試官問:可否模擬實現JS的call和apply方法
面試官問:可否模擬實現JS的bind方法
面試官問:可否模擬實現JS的new操做符

關於

做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。
若川的博客,使用vuepress重構了,閱讀體驗可能更好些
掘金專欄,歡迎關注~
segmentfault前端視野專欄,歡迎關注~
知乎前端視野專欄,歡迎關注~
語雀前端視野專欄,新增語雀專欄,歡迎關注~
github blog,相關源碼和資源都放在這裏,求個star^_^~

歡迎加微信交流 微信公衆號

可能比較有趣的微信公衆號,長按掃碼關注。歡迎加我微信ruochuan12(註明來源,基原本者不拒),拉你進【前端視野交流羣】,長期交流學習~

若川視野
若川視野

本文使用 mdnice 排版

相關文章
相關標籤/搜索