一、 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 還需要細細體會。沒有真正領悟到其精華之處。
謝謝大家。