redux的原始碼解析

aGod發表於2018-05-17

一、 redux出現的動機

1. Javascript 需要管理比任何時候都要多的state
2. state 在什麼時候,由於什麼原因,如何變化已然不受控制。
3. 來自前端開發領域的新需求
4. 我們總是將兩個難以理清的概念混淆在一起:變化和非同步。
5. Redux 檢視讓state 的變化變得可預測。

二、 核心概念

 1. 想要更新state中的資料,你需要發起一個action,Action就是一個普通的JavaScript 物件用來描述發生了什麼。為了把actin 和state串起來開發一些函式,就是redcer。

三、 三大原則

1. 單一資料來源 整個應用的state被儲存在一棵objecttree中, 並且這個 object tree 只 存在於一個唯一的store 中。

2. state 是隻讀的,唯一改變state的方法就是觸發action,action 是一個用於描述已發 生事件的普通物件。(確保了檢視和網路請求不能直接修改state,只能表達想要修改的意圖)

3. 使用純函式來執行修改為了描述action如何改變state tree ,你需要編寫reducers。

四、 原始碼解析

1. 入口檔案index.js
import createStore from `./createStore`
import combineReducers from `./combineReducers`
import bindActionCreators from `./bindActionCreators`
import applyMiddleware from `./applyMiddleware`
import compose from `./compose`
import warning from `./utils/warning`
import __DO_NOT_USE__ActionTypes from `./utils/actionTypes`

/*
 * This is a dummy function to check if the function name has been altered by minification.
 * If the function has been minified and NODE_ENV !== `production`, warn the user.
 */

// 判斷檔案是否被壓縮了
function isCrushed() {}

if (
  process.env.NODE_ENV !== `production` &&
  typeof isCrushed.name === `string` &&
  isCrushed.name !== `isCrushed`
) {
  warning(
    `You are currently using minified code outside of NODE_ENV === "production". ` +
      `This means that you are running a slower development build of Redux. ` +
      `You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ` +
      `or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ` +
      `to ensure you have the correct code for your production build.`
  )
}
/*
  從入口檔案可以看出 redux 對外暴露了5個API。
  createStore ,
  combineReducers,
  bindActionCreators,
 applyMiddleware,
 compose,
 */
export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}
2. 對外暴露的第一個API createStore => createStore.js
  1 import $$observable from `symbol-observable`
  2 
  3 import ActionTypes from `./utils/actionTypes`
  4 import isPlainObject from `./utils/isPlainObject`
  5 
  6 /**
  7  * Creates a Redux store that holds the state tree.
  8  * The only way to change the data in the store is to call `dispatch()` on it.
  9  *
 10  * There should only be a single store in your app. To specify how different
 11  * parts of the state tree respond to actions, you may combine several reducers
 12  * into a single reducer function by using `combineReducers`.
 13  *
 14  * @param {Function} reducer A function that returns the next state tree, given
 15  * the current state tree and the action to handle.
 16  *
 17  * @param {any} [preloadedState] The initial state. You may optionally specify it
 18  * to hydrate the state from the server in universal apps, or to restore a
 19  * previously serialized user session.
 20  * If you use `combineReducers` to produce the root reducer function, this must be
 21  * an object with the same shape as `combineReducers` keys.
 22  *
 23  * @param {Function} [enhancer] The store enhancer. You may optionally specify it
 24  * to enhance the store with third-party capabilities such as middleware,
 25  * time travel, persistence, etc. The only store enhancer that ships with Redux
 26  * is `applyMiddleware()`.
 27  *
 28  * @returns {Store} A Redux store that lets you read the state, dispatch actions
 29  * and subscribe to changes.
 30  */
 31 /*
 32   從原始碼上可以看出 createStore 是一個函式。接收三個引數 reducer, preloadeState, enhancer
 33  */
 34 export default function createStore(reducer, preloadedState, enhancer) {
 35   // 如果 preloadeState 是一個函式 && enhancer未定義preloadeState 和 enhancer交換位置
 36   if (typeof preloadedState === `function` && typeof enhancer === `undefined`) {
 37     enhancer = preloadedState
 38     preloadedState = undefined
 39   }
 40   //
 41   if (typeof enhancer !== `undefined`) {
 42     if (typeof enhancer !== `function`) {
 43       throw new Error(`Expected the enhancer to be a function.`)
 44     }
 45     // 上面兩個判斷是為了確保 enchancer是個函式
 46     return enhancer(createStore)(reducer, preloadedState)
 47   }
 48 
 49   // reducer必須是 個函式,如果不是個函式給出友好的 提示
 50   if (typeof reducer !== `function`) {
 51     throw new Error(`Expected the reducer to be a function.`)
 52   }
 53 
 54   let currentReducer = reducer    // 把reducer 暫存起來
 55   let currentState = preloadedState // 把preloadeState暫存起來
 56   let currentListeners = []
 57   let nextListeners = currentListeners
 58   let isDispatching = false   //判斷是否正處於dispatch中
 59 
 60   // 如果 nextListeners 和 currrentListeners 都指向一個記憶體空間的時候, 深複製一份出來。確保兩個之間不會相互影響。
 61   function ensureCanMutateNextListeners() {
 62     if (nextListeners === currentListeners) {
 63       nextListeners = currentListeners.slice()
 64     }
 65   }
 66 
 67   /**
 68    * Reads the state tree managed by the store.
 69    *
 70    * @returns {any} The current state tree of your application.
 71    */
 72   // 獲取目前的 state的值。
 73   function getState() {
 74     if (isDispatching) {
 75       throw new Error(
 76         `You may not call store.getState() while the reducer is executing. ` +
 77           `The reducer has already received the state as an argument. ` +
 78           `Pass it down from the top reducer instead of reading it from the store.`
 79       )
 80     }
 81 
 82     return currentState
 83   }
 84 
 85   /**
 86    * Adds a change listener. It will be called any time an action is dispatched,
 87    * and some part of the state tree may potentially have changed. You may then
 88    * call `getState()` to read the current state tree inside the callback.
 89    *
 90    * You may call `dispatch()` from a change listener, with the following
 91    * caveats:
 92    *
 93    * 1. The subscriptions are snapshotted just before every `dispatch()` call.
 94    * If you subscribe or unsubscribe while the listeners are being invoked, this
 95    * will not have any effect on the `dispatch()` that is currently in progress.
 96    * However, the next `dispatch()` call, whether nested or not, will use a more
 97    * recent snapshot of the subscription list.
 98    *
 99    * 2. The listener should not expect to see all state changes, as the state
100    * might have been updated multiple times during a nested `dispatch()` before
101    * the listener is called. It is, however, guaranteed that all subscribers
102    * registered before the `dispatch()` started will be called with the latest
103    * state by the time it exits.
104    *
105    * @param {Function} listener A callback to be invoked on every dispatch.
106    * @returns {Function} A function to remove this change listener.
107    */
108   // 經典的訂閱函式
109   function subscribe(listener) {
110     if (typeof listener !== `function`) {
111       throw new Error(`Expected the listener to be a function.`)
112     }
113 
114     if (isDispatching) {
115       throw new Error(
116         `You may not call store.subscribe() while the reducer is executing. ` +
117           `If you would like to be notified after the store has been updated, subscribe from a ` +
118           `component and invoke store.getState() in the callback to access the latest state. ` +
119           `See https://redux.js.org/api-reference/store#subscribe(listener) for more details.`
120       )
121     }
122     // 閉包的經典應用 每次訂閱一個事件的時候,都有一個內部狀態, 用於後續的取消訂閱
123     let isSubscribed = true
124     // 深複製一份監聽物件
125     ensureCanMutateNextListeners()
126     // 把每個監聽物件都放置於一個陣列中,儲存下來,(精華之處,對閉包的使用登峰造極)
127     nextListeners.push(listener)
128     // 當註冊一個監聽事件的返回一個函式,呼叫這個函式可以取消訂閱,具體操作方法就是從監聽的陣列中移出掉。
129     return function unsubscribe() {
130       // 防止重複取消訂閱
131       if (!isSubscribed) {
132         return
133       }
134 
135       if (isDispatching) {
136         throw new Error(
137           `You may not unsubscribe from a store listener while the reducer is executing. ` +
138             `See https://redux.js.org/api-reference/store#subscribe(listener) for more details.`
139         )
140       }
141       // 對應上面那條,防止重複取消訂閱
142       isSubscribed = false
143 
144       ensureCanMutateNextListeners()
145       const index = nextListeners.indexOf(listener)
146         // 刪除陣列中某一項的方法 splice
147       nextListeners.splice(index, 1)
148     }
149   }
150 
151   /**
152    * Dispatches an action. It is the only way to trigger a state change.
153    *
154    * The `reducer` function, used to create the store, will be called with the
155    * current state tree and the given `action`. Its return value will
156    * be considered the **next** state of the tree, and the change listeners
157    * will be notified.
158    *
159    * The base implementation only supports plain object actions. If you want to
160    * dispatch a Promise, an Observable, a thunk, or something else, you need to
161    * wrap your store creating function into the corresponding middleware. For
162    * example, see the documentation for the `redux-thunk` package. Even the
163    * middleware will eventually dispatch plain object actions using this method.
164    *
165    * @param {Object} action A plain object representing “what changed”. It is
166    * a good idea to keep actions serializable so you can record and replay user
167    * sessions, or use the time travelling `redux-devtools`. An action must have
168    * a `type` property which may not be `undefined`. It is a good idea to use
169    * string constants for action types.
170    *
171    * @returns {Object} For convenience, the same action object you dispatched.
172    *
173    * Note that, if you use a custom middleware, it may wrap `dispatch()` to
174    * return something else (for example, a Promise you can await).
175    */
176   // 派發一個事件
177   function dispatch(action) {
178     // p、判斷action是否是個物件
179     if (!isPlainObject(action)) {
180       throw new Error(
181         `Actions must be plain objects. ` +
182           `Use custom middleware for async actions.`
183       )
184     }
185     // 嚴格控制 action 的書寫格式  { type: `INCREMENT`}
186     if (typeof action.type === `undefined`) {
187       throw new Error(
188         `Actions may not have an undefined "type" property. ` +
189           `Have you misspelled a constant?`
190       )
191     }
192 
193     if (isDispatching) {
194       throw new Error(`Reducers may not dispatch actions.`)
195     }
196     // isDipatching 也是閉包的經典用法
197       /*
198         觸發 dispatch的時候 把 isDispatching 改為 true。 照應全篇中對 dispatching 正在觸發的時候的判斷
199         finally 執行完畢的時候 置為 false
200        */
201     try {
202       isDispatching = true
203       // 獲取最新 的state 值。 currentState 經典之處閉包
204       currentState = currentReducer(currentState, action)
205     } finally {
206       isDispatching = false
207     }
208 
209     // 對監聽物件從新賦值 其實裡面每個listener都是一個函式。 於subscribe相對應
210     // 當每發生一個dispatch 事件的時候, 都迴圈呼叫,觸發監聽事件
211     const listeners = (currentListeners = nextListeners)
212     for (let i = 0; i < listeners.length; i++) {
213       const listener = listeners[i]
214       listener()
215     }
216 
217     return action
218   }
219 
220   /**
221    * Replaces the reducer currently used by the store to calculate the state.
222    *
223    * You might need this if your app implements code splitting and you want to
224    * load some of the reducers dynamically. You might also need this if you
225    * implement a hot reloading mechanism for Redux.
226    *
227    * @param {Function} nextReducer The reducer for the store to use instead.
228    * @returns {void}
229    */
230   // 替換 reducer 用 新的 reducer替換以前的reducer 引數同樣必須是函式
231   function replaceReducer(nextReducer) {
232     if (typeof nextReducer !== `function`) {
233       throw new Error(`Expected the nextReducer to be a function.`)
234     }
235 
236     currentReducer = nextReducer
237     dispatch({ type: ActionTypes.REPLACE })
238   }
239 
240   /**
241    * Interoperability point for observable/reactive libraries.
242    * @returns {observable} A minimal observable of state changes.
243    * For more information, see the observable proposal:
244    * https://github.com/tc39/proposal-observable
245    */
246   // 觀察模式
247   function observable() {
248     const outerSubscribe = subscribe
249     return {
250       /**
251        * The minimal observable subscription method.
252        * @param {Object} observer Any object that can be used as an observer.
253        * The observer object should have a `next` method.
254        * @returns {subscription} An object with an `unsubscribe` method that can
255        * be used to unsubscribe the observable from the store, and prevent further
256        * emission of values from the observable.
257        */
258       subscribe(observer) {
259         if (typeof observer !== `object` || observer === null) {
260           throw new TypeError(`Expected the observer to be an object.`)
261         }
262 
263         function observeState() {
264           if (observer.next) {
265             observer.next(getState())
266           }
267         }
268 
269         observeState()
270         const unsubscribe = outerSubscribe(observeState)
271         return { unsubscribe }
272       },
273 
274       [$$observable]() {
275         return this
276       }
277     }
278   }
279 
280   // When a store is created, an "INIT" action is dispatched so that every
281   // reducer returns their initial state. This effectively populates
282   // the initial state tree.
283     // 觸發一state 樹
284   dispatch({ type: ActionTypes.INIT })
285   /*
286      由此可以看出 呼叫 createStore(); 後。對外暴露的方法
287      1. dispatch
288      2. subscribe
289      3. getState
290      4. replaceReducer
291      5.觀察模式
292      const store = createStore(reducer, preloadedState, enchancer);
293      store.dispatch(action);
294      store.getState(); // 為什麼這個方法能夠獲得 state的值。因為 currentState 的閉包實現。
295      store.subscribe(() => console.log(store.getState()));
296      store.replaceReducer(reducer);
297      總結:縱觀createStore方法的實現,其實都是建立在閉包的基礎之上。可謂是把閉包用到了極致。
298    */
299   return {
300     dispatch,
301     subscribe,
302     getState,
303     replaceReducer,
304     [$$observable]: observable
305   }
306 }
3. combineReducers.js
  1 import ActionTypes from `./utils/actionTypes`
  2 import warning from `./utils/warning`
  3 import isPlainObject from `./utils/isPlainObject`
  4 
  5 function getUndefinedStateErrorMessage(key, action) {
  6   const actionType = action && action.type
  7   const actionDescription =
  8     (actionType && `action "${String(actionType)}"`) || `an action`
  9 
 10   return (
 11     `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
 12     `To ignore an action, you must explicitly return the previous state. ` +
 13     `If you want this reducer to hold no value, you can return null instead of undefined.`
 14   )
 15 }
 16 
 17 function getUnexpectedStateShapeWarningMessage(
 18   inputState,
 19   reducers,
 20   action,
 21   unexpectedKeyCache
 22 ) {
 23   const reducerKeys = Object.keys(reducers)
 24   const argumentName =
 25     action && action.type === ActionTypes.INIT
 26       ? `preloadedState argument passed to createStore`
 27       : `previous state received by the reducer`
 28 
 29   if (reducerKeys.length === 0) {
 30     return (
 31       `Store does not have a valid reducer. Make sure the argument passed ` +
 32       `to combineReducers is an object whose values are reducers.`
 33     )
 34   }
 35 
 36   if (!isPlainObject(inputState)) {
 37     return (
 38       `The ${argumentName} has unexpected type of "` +
 39       {}.toString.call(inputState).match(/s([a-z|A-Z]+)/)[1] +
 40       `". Expected argument to be an object with the following ` +
 41       `keys: "${reducerKeys.join(`", "`)}"`
 42     )
 43   }
 44 
 45   const unexpectedKeys = Object.keys(inputState).filter(
 46     key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
 47   )
 48 
 49   unexpectedKeys.forEach(key => {
 50     unexpectedKeyCache[key] = true
 51   })
 52 
 53   if (action && action.type === ActionTypes.REPLACE) return
 54 
 55   if (unexpectedKeys.length > 0) {
 56     return (
 57       `Unexpected ${unexpectedKeys.length > 1 ? `keys` : `key`} ` +
 58       `"${unexpectedKeys.join(`", "`)}" found in ${argumentName}. ` +
 59       `Expected to find one of the known reducer keys instead: ` +
 60       `"${reducerKeys.join(`", "`)}". Unexpected keys will be ignored.`
 61     )
 62   }
 63 }
 64 
 65 function assertReducerShape(reducers) {
 66   Object.keys(reducers).forEach(key => {
 67     const reducer = reducers[key]
 68     const initialState = reducer(undefined, { type: ActionTypes.INIT })
 69 
 70     if (typeof initialState === `undefined`) {
 71       throw new Error(
 72         `Reducer "${key}" returned undefined during initialization. ` +
 73           `If the state passed to the reducer is undefined, you must ` +
 74           `explicitly return the initial state. The initial state may ` +
 75           `not be undefined. If you don`t want to set a value for this reducer, ` +
 76           `you can use null instead of undefined.`
 77       )
 78     }
 79 
 80     if (
 81       typeof reducer(undefined, {
 82         type: ActionTypes.PROBE_UNKNOWN_ACTION()
 83       }) === `undefined`
 84     ) {
 85       throw new Error(
 86         `Reducer "${key}" returned undefined when probed with a random type. ` +
 87           `Don`t try to handle ${
 88             ActionTypes.INIT
 89           } or other actions in "redux/*" ` +
 90           `namespace. They are considered private. Instead, you must return the ` +
 91           `current state for any unknown actions, unless it is undefined, ` +
 92           `in which case you must return the initial state, regardless of the ` +
 93           `action type. The initial state may not be undefined, but can be null.`
 94       )
 95     }
 96   })
 97 }
 98 
 99 /**
100  * Turns an object whose values are different reducer functions, into a single
101  * reducer function. It will call every child reducer, and gather their results
102  * into a single state object, whose keys correspond to the keys of the passed
103  * reducer functions.
104  *
105  * @param {Object} reducers An object whose values correspond to different
106  * reducer functions that need to be combined into one. One handy way to obtain
107  * it is to use ES6 `import * as reducers` syntax. The reducers may never return
108  * undefined for any action. Instead, they should return their initial state
109  * if the state passed to them was undefined, and the current state for any
110  * unrecognized action.
111  *
112  * @returns {Function} A reducer function that invokes every reducer inside the
113  * passed object, and builds a state object with the same shape.
114  */
115 
116 /*
117   combineReducers 顧名思義就是合併reduces的一個方法。
118   1. 為了專案便於維護與管理我們就需要拆按模組拆分reducers。
119   2. 而combineReducers就是為了解決這個的問題的。
120 
121  */
122 export default function combineReducers(reducers) { // 引數reducers 是一個物件
123   const reducerKeys = Object.keys(reducers) // 獲取reducers的k
124   const finalReducers = {}
125   for (let i = 0; i < reducerKeys.length; i++) {
126     const key = reducerKeys[i]
127 
128     if (process.env.NODE_ENV !== `production`) {
129       if (typeof reducers[key] === `undefined`) {
130         warning(`No reducer provided for key "${key}"`)
131       }
132     }
133 
134     // 深複製一份reducers出來, 防止後續操作出現不可控因素
135     if (typeof reducers[key] === `function`) {
136       finalReducers[key] = reducers[key]
137     }
138   }
139   const finalReducerKeys = Object.keys(finalReducers)
140 
141   let unexpectedKeyCache
142   if (process.env.NODE_ENV !== `production`) {
143     unexpectedKeyCache = {}
144   }
145 
146   let shapeAssertionError
147   try {
148     assertReducerShape(finalReducers)
149   } catch (e) {
150     shapeAssertionError = e
151   }
152   // 閉包的運用, 把合併的 reducer儲存下來。
153   return function combination(state = {}, action) {
154     if (shapeAssertionError) {
155       throw shapeAssertionError
156     }
157 
158     if (process.env.NODE_ENV !== `production`) {
159       const warningMessage = getUnexpectedStateShapeWarningMessage(
160         state,
161         finalReducers,
162         action,
163         unexpectedKeyCache
164       )
165       if (warningMessage) {
166         warning(warningMessage)
167       }
168     }
169 
170     let hasChanged = false
171     const nextState = {}
172     for (let i = 0; i < finalReducerKeys.length; i++) {
173       const key = finalReducerKeys[i]
174       const reducer = finalReducers[key]
175       const previousStateForKey = state[key]
176       // 把合併的時候的key值作為Key值為標準。 在迴圈遍歷的時候取出對應的 reducers 觸發 reducer函式。
177       /*
178         其實對應的createStore.js中的
179         try {
180           isDispatching = true
181           // 獲取最新 的state 值。 currentState 經典之處閉包
182           currentState = currentReducer(currentState, action)
183         } finally {
184           isDispatching = false
185         }  
186        */
187       const nextStateForKey = reducer(previousStateForKey, action)
188       if (typeof nextStateForKey === `undefined`) {
189         const errorMessage = getUndefinedStateErrorMessage(key, action)
190         throw new Error(errorMessage)
191       }
192       nextState[key] = nextStateForKey
193       hasChanged = hasChanged || nextStateForKey !== previousStateForKey
194     }
195     return hasChanged ? nextState : state
196   }
197 }
198 /**
199  * 使用方法
200  * const reducers = combineReducers({ reducer1, reducer2 });
201  * const store = createStore(reducers, preloadedState, enchancer);
202  */
4. bindActionCreators.js
 1 // 主要這個函式
 2 function bindActionCreator(actionCreator, dispatch) {
 3   return function() {
 4     return dispatch(actionCreator.apply(this, arguments))
 5   }
 6 }
 7 
 8 /**
 9  * Turns an object whose values are action creators, into an object with the
10  * same keys, but with every function wrapped into a `dispatch` call so they
11  * may be invoked directly. This is just a convenience method, as you can call
12  * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
13  *
14  * For convenience, you can also pass a single function as the first argument,
15  * and get a function in return.
16  *
17  * @param {Function|Object} actionCreators An object whose values are action
18  * creator functions. One handy way to obtain it is to use ES6 `import * as`
19  * syntax. You may also pass a single function.
20  *
21  * @param {Function} dispatch The `dispatch` function available on your Redux
22  * store.
23  *
24  * @returns {Function|Object} The object mimicking the original object, but with
25  * every action creator wrapped into the `dispatch` call. If you passed a
26  * function as `actionCreators`, the return value will also be a single
27  * function.
28  */
29 
30 /*
31    接受兩個引數,一個action creator, 一個是 value的 action creator的物件。
32    dispatch 。 一個由 Store 實列 提供的dispatch的函式。 看createStore.js原始碼就可以做知道其中原理。
33  */
34 export default function bindActionCreators(actionCreators, dispatch) {
35   if (typeof actionCreators === `function`) {
36     return bindActionCreator(actionCreators, dispatch)
37   }
38 
39   if (typeof actionCreators !== `object` || actionCreators === null) {
40     throw new Error(
41       `bindActionCreators expected an object or a function, instead received ${
42         actionCreators === null ? `null` : typeof actionCreators
43       }. ` +
44         `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
45     )
46   }
47 
48   const keys = Object.keys(actionCreators)
49   const boundActionCreators = {}
50   for (let i = 0; i < keys.length; i++) {
51     const key = keys[i]
52     const actionCreator = actionCreators[key]
53     if (typeof actionCreator === `function`) {
54       boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
55     }
56   }
57   /*
58      一個與原物件類似的物件,只不過這個物件的 value 都是會直接 dispatch 原 action creator 返回的結果的函式。
59      如果傳入一個單獨的函式作為 actionCreators,那麼返回的結果也是一個單獨的函式。
60      本來觸發 action 的方法是 store.dispatch(action);
61      經過這個方法封裝後 可以直接呼叫函式名字
62      aa(`引數`);
63    */
64   return boundActionCreators
65 }

5. applyMiddleware.js  在redux 中最難理解的一個函式。

import compose from `./compose`
import createStore from "./createStore";

/**
 * Creates a store enhancer that applies middleware to the dispatch method
 * of the Redux store. This is handy for a variety of tasks, such as expressing
 * asynchronous actions in a concise manner, or logging every action payload.
 *
 * See `redux-thunk` package as an example of the Redux middleware.
 *
 * Because middleware is potentially asynchronous, this should be the first
 * store enhancer in the composition chain.
 *
 * Note that each middleware will be given the `dispatch` and `getState` functions
 * as named arguments.
 *
 * @param {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
// 通過看原始碼知道 applyMiddleware返回一個高階函式。
/*
  applyMiddleware的使用地方
  const store = createStore(reducer,{}, applyMiddleware(...middlewares));
  
  由此可以看出 applyMiddlewares 的使用方法主要是和 createStore.js 中 createStore方法的第三個引數對應。翻開原始碼
  if (typeof enhancer !== `undefined`) {
    if (typeof enhancer !== `function`) {
      throw new Error(`Expected the enhancer to be a function.`)
    }
    // 上面兩個判斷是為了確保 enchancer是個函式
    return enhancer(createStore)(reducer, preloadedState)
  }
 */
export default function applyMiddleware(...middlewares) {
  // createSotre 中 的第三個引數 enhancer
  return createStore => (...args) => {
    // 通過對應的程式碼可以發現其實 ...aregs 對應的是  reducer, preloadedState
    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.`
      )
    }
      // 定義中介軟體必須滿足的條件。 API   getState, dispatch();
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)  
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))

      /**
       * 

      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)))
      }
       compose函式 主要是 利用陣列 reducer 方法對引數的處理。

  */
      dispatch = compose(...chain)(store.dispatch)
    // 通過這個返回值我們可以知道 在createStore.js中enchancer的返回值。
    return {
      ...store,
      dispatch
    }
  }
}

/**
 *  applyMiddlewares函式比較難理解。 多看幾個中介軟體,比如 logger 和 redux-thunk 等。對該方法能夠更深的理解。
 */

五、 redux的總結

通過閱讀redux的原始碼,印象最深的就是如何手動寫個訂閱模式,資料改變的時候,如何觸發所有監聽事件。閉包的運用登峰造極。其中最難的兩個函式 applyMiddlewares 和compose.js 還需要細細體會。沒有真正領悟到其精華之處。

謝謝大家。

 

相關文章