運用redux有一段時間了,包括redux-thunk和redux-saga處理異步action都有必定的涉及,如今技術棧轉向阿里的dva+antd,好用得不要不要的,可是須要知己知彼要對react家族有一點源碼上的深刻了,就從redux開始吧。javascript
redux源碼是那麼簡潔、清晰、乾淨,讓我忍不住一口氣所有看完了還意猶未盡的寫一篇隨筆,mark一下,過段時間回頭再從新細細評味學習一波。原諒我把整片代碼貼出來,由於我懶啊,我會盡可能把代碼註解寫詳細一點。java
redux對外暴露出的api,這裏能夠看出文件結構和功能塊相關分得很清晰。react
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. */ // isCrushed 函數僅僅用於判斷代碼是否處於壓縮並再壓縮時拋出警告 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.' ) } export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose, __DO_NOT_USE__ActionTypes }
redux最重要的api,用於構建Store,並在建立之後還有本身的api,結構十分的清晰webpack
import $$observable from 'symbol-observable' import ActionTypes from './utils/actionTypes' import isPlainObject from './utils/isPlainObject' /** * Creates a Redux store that holds the state tree. * The only way to change the data in the store is to call `dispatch()` on it. * * There should only be a single store in your app. To specify how different * parts of the state tree respond to actions, you may combine several reducers * into a single reducer function by using `combineReducers`. * * @param {Function} reducer A function that returns the next state tree, given * the current state tree and the action to handle. * @param {Function} reducer:返回一個完整獨立全新的state tree,接受參數(當前state,須要觸發actions集合) * * @param {any} [preloadedState] The initial state. You may optionally specify it * to hydrate the state from the server in universal apps, or to restore a * previously serialized user session. * If you use `combineReducers` to produce the root reducer function, this must be * an object with the same shape as `combineReducers` keys. * @param {any} [preloadedState] 初始化state,不是必需,能夠與服務端渲染水合初始狀態, * 若是使用combineReduers必需與其中key值一一對應,查看combineReduers實現 * * @param {Function} [enhancer] The store enhancer. You may optionally specify it * to enhance the store with third-party capabilities such as middleware, * time travel, persistence, etc. The only store enhancer that ships with Redux * is `applyMiddleware()`. * @param {Function} [enhancer] store的外掛,經常使用middleware中間件,其餘暫時不去深刻 * * @returns {Store} A Redux store that lets you read the state, dispatch actions * and subscribe to changes. */ export default function createStore(reducer, preloadedState, enhancer) { // 判斷參數個數,相似jq===on參數處理方式 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } // 首先判斷enhancer(常見的即是middlewares中間件),循環回調將跳過此處 if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } // middlewares詳細解釋返回值, return enhancer(createStore)(reducer, preloadedState) } // redux爲了方便開發者作了不少友好的提示,只有深刻源碼才知道的良苦用心,reducer只接受是一個函數 if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } // 保存當前的傳入值,後邊會涉及到這些值的來回更迭 let currentReducer = reducer let currentState = preloadedState let currentListeners = [] // 監聽函數事件隊列 爲何不寫成let nextListeners = currentListeners = [] 風格嗎? // 還有爲何須要兩個listener數組來存放呢?答案再訂閱和dispatch裏面 let nextListeners = currentListeners // 是否處於dispatch過程當中,我也好奇異步dispatch的時候將怎麼變化 let isDispatching = false // 當前監聽隊列與接下來的監聽隊列指向同一個數組時,slice出新的數組 function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { // 仍是爲了去除引用,完成next和current的交替,能夠將next看做是current的快照 nextListeners = currentListeners.slice() } } /** * Reads the state tree managed by the store. * * @returns {any} The current state tree of your application. */ // 只有在非觸發狀態才能經過api獲取當前state的快照 function getState() { 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.' ) } // 注意這裏閉包了,直接給裏currentState,且他是時常變化的值,須要再穩定的時候取值 return currentState } /** * Adds a change listener. It will be called any time an action is dispatched, * and some part of the state tree may potentially have changed. You may then * call `getState()` to read the current state tree inside the callback. * * You may call `dispatch()` from a change listener, with the following * caveats: * * 1. The subscriptions are snapshotted just before every `dispatch()` call. * If you subscribe or unsubscribe while the listeners are being invoked, this * will not have any effect on the `dispatch()` that is currently in progress. * However, the next `dispatch()` call, whether nested or not, will use a more * recent snapshot of the subscription list. * * 2. The listener should not expect to see all state changes, as the state * might have been updated multiple times during a nested `dispatch()` before * the listener is called. It is, however, guaranteed that all subscribers * registered before the `dispatch()` started will be called with the latest * state by the time it exits. * * @param {Function} listener A callback to be invoked on every dispatch. * @returns {Function} A function to remove this change listener. */ // dva裏面也有監聽器,下次去看看源碼 function subscribe(listener) { // 老規矩容錯 if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } 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#subscribe(listener) for more details.' ) } // 監聽已經完成標誌,用於清除監聽 let isSubscribed = true // 函數入其名,獲得nextListeners ensureCanMutateNextListeners() // 將監聽的事件添加到nextListeners隊列中,注意可能添加了隊列中已有的事件,無論執行兩遍 nextListeners.push(listener) // 返回函數能夠移除事件監聽 return function unsubscribe() { // 只移除一次 if (!isSubscribed) { return } 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#subscribe(listener) for more details.' ) } // 控制標誌位,很少餘移除 isSubscribed = false // 再次獲得新的nextListeners ensureCanMutateNextListeners() // 感受這裏若是註冊兩個相同的事件,會移除前面那個,不知道會不會有問題 const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } } /** * Dispatches an action. It is the only way to trigger a state change. * * The `reducer` function, used to create the store, will be called with the * current state tree and the given `action`. Its return value will * be considered the **next** state of the tree, and the change listeners * will be notified. * * The base implementation only supports plain object actions. If you want to * dispatch a Promise, an Observable, a thunk, or something else, you need to * wrap your store creating function into the corresponding middleware. For * example, see the documentation for the `redux-thunk` package. Even the * middleware will eventually dispatch plain object actions using this method. * * @param {Object} action A plain object representing 「what changed」. It is * a good idea to keep actions serializable so you can record and replay user * sessions, or use the time travelling `redux-devtools`. An action must have * a `type` property which may not be `undefined`. It is a good idea to use * string constants for action types. * * @returns {Object} For convenience, the same action object you dispatched. * * Note that, if you use a custom middleware, it may wrap `dispatch()` to * return something else (for example, a Promise you can await). */ // 至關重要的方法,純粹的dispatch的參數只接受Object類型的,thunk就是對它進行處理進而能傳入 // function用回調的形式從新dispatch,下次再詳細thunk和saga function dispatch(action) { // isPlainObject用於判斷是不是對象 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } // action關鍵字限制爲 type,爲了避免形成命名上的困惑通常type前綴我會設置與文件夾同名 if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } // 正在dispatch,這裏什麼狀況會出現這個警告呢!!! // 在dispatch中嵌套的調用dispatch會觸發這類警告,多是擔憂dispatchA(dispatchB(dispatchA))的嵌套循環問題把 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true // 進行reduce操做,記得參數是當前state和action對象,返回全新的State對象,這一手操做是react就高興了 currentState = currentReducer(currentState, action) } finally { //完成一波reducer記得復位標誌,表示個人完成dispatch。 isDispatching = false } // 執行事件隊列前纔拿到最新的listenters,在此以前可能會出現訂閱與退訂的嵌套等問題,暫存的nextlisteners能夠保證dispatch的正常執行 // 假如出現listenerA(){store.subscribe(listenerA);}的嵌套狀況,listeners的長度將再每一次執行延長一直至無限長 // 固然若是採用len = listeners.length;直接固定循環次數能夠解決如今的狀況,可是退訂等事件的發生也會出現問題,因此暫存是最安全的作法 const listeners = (currentListeners = nextListeners) // 爲何要用for循環不用foreach,想一想forEach對空元素的處理的性能問題把 for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] // 爲何不直接listeners[i]()執行呢?而是負值單獨調用呢? // 賦值以後this的指向再也不是listens而是window listener() } // 返回了整個action對象 return action } /** * Replaces the reducer currently used by the store to calculate the state. * * You might need this if your app implements code splitting and you want to * load some of the reducers dynamically. You might also need this if you * implement a hot reloading mechanism for Redux. * * @param {Function} nextReducer The reducer for the store to use instead. * @returns {void} */ // 替換reducer函數 function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer // 觸發私有的replace action dispatch({ type: ActionTypes.REPLACE }) } /** * Interoperability point for observable/reactive libraries. * @returns {observable} A minimal observable of state changes. * For more information, see the observable proposal: * https://github.com/tc39/proposal-observable */ // 能夠看做是對redux觀察者的一個擴展,可做爲全局的每次dispatch都執行方法入口 function observable() { const outerSubscribe = subscribe return { /** * The minimal observable subscription method. * @param {Object} observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns {subscription} An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. */ // 須要傳入一個帶next方法的對象,將返回退訂鉤子 subscribe(observer) { if (typeof observer !== 'object' || observer === null) { throw new TypeError('Expected the observer to be an object.') } function observeState() { if (observer.next) { // next方法將得到當時的store observer.next(getState()) } } observeState() const unsubscribe = outerSubscribe(observeState) // 返回包含退訂對象 return { unsubscribe } }, // 用於獲取observeable,這名字取的。。。 [$$observable]() { return this } } } // When a store is created, an "INIT" action is dispatched so that every // reducer returns their initial state. This effectively populates // the initial state tree. // 初始化store對象 dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
一個關於reduce的函數設計,須要特別拿出來講說git
/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */ export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } // reduce用在這裏太巧妙了,奇妙的洋蔥函數,好吧也沒那麼奇妙 // compose(f, g, h)(...args) 等同於 f(g(h(...args)))在後面會造成一個currying函數 return funcs.reduce((a, b) => (...args) => a(b(...args))) }
重點,廢話很少說直接上代碼github
import compose from './compose' /** * 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. */ // 很是精髓的一段代碼 // createStore中以enhancer(createStore)(reducer, preloadedState)調用 export default function applyMiddleware(...middlewares) { // 二階函數參數...args對應reducer, preloadedState return createStore => (...args) => { const store = createStore(...args) // 這裏不該該是 const dispatch = store.dispatch??有些版本出現這樣 // 猜想:這裏避免使用到沒有中間件處理過的disptch,後面將傳入完整的store.dispatch做爲根參數, // 求解若是這裏只是個警告函數,每一箇中間件接受到的({ dispatch, getState })又是什麼呢? // 好吧,我又又想到答案了,再下面 let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } // 中間件獲取到的能力,獲取store快照(isDispatching???怎麼判斷的),觸發reducer const middlewareAPI = { getState: store.getState, // 我就是上面的答案:這裏dispatch用閉包並非直接的引用,dispatch會根據dispatch = compose(...chain)(store.dispatch) // 而變化,在此以前調用dispatch會爆出警告!!! dispatch: (...args) => dispatch(...args) } // middleware應該是高階函數,return 了一個function在chain數組 // 對應thunk的createThunkMiddleware({dispatch, getStat}),這裏只要注意傳入了什麼,thunk內詳細分析怎麼運行中間件 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 將store.dispatch做爲二階參數傳入,最終將對應中間件最內層的action, // 注意下面這個例子: // applyMiddleware(log1, log2, log3),在這裏經過洋蔥函數的處理dispatch變成log11(log22(log33(store.dispatch)))這樣一個函數 // log11是log1({dispatch, getState})的返回函數,以此類推,這種結構也限定裏中間件函數的基本結構是 // ({ dispatch, getState }) => next => action => {} ,最開始可能對這個結構很迷糊,why,看下面
// 這裏就造成一個第一個參數爲store.dispatch的currying函數,以後傳入的action,dispatch(action)都將一併視爲compose(...chain)(store.dispatch)(action)
dispatch = compose(...chain)(store.dispatch) // 對應返回在了createStore裏即Store,全新的dispatch誕生 return { ...store, dispatch } } } // 以爲把redux-thunk的代碼一塊兒貼出來纔有參照性 function createThunkMiddleware(extraArgument) { // 其實thunk內容實在是簡潔,判斷類型將dispatch放入到函數裏面,這裏的dispatch是層層包裝過的 // 那麼咱們來分析針對整個箭頭函數和中間件結構進行分析一下 // log11的next(action)對應log22(log33(action)), // log22的next(action)對應log33(action), // log33的next對應store.dispatch,最後返回一個須要傳參爲action的函數,
// action對應{type: 'TO_DO',text: '1'}一直傳遞無變化,只有next變化,造成一個層層執行 // // 而執行順序有點像冒泡,從外到裏再從裏到外,若是上面的log每一個都有before和after的話,順序將是 // log11.before > log22.before > log33.before > store.dispatch > log33.after > log22.after > log11.after > end // 每個中間件將對dispatch以前和以後做些動做 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; // 調用方式 createStore(reducer, applyMiddleware(thunk))
其餘還有幾個文件就不貼出來了,只有深刻到源碼才能感覺代碼之美,redux簡直稱爲精粹。redux就是典型的百行代碼千行文檔,也只有看了源碼才能略微理解其用途和技巧。我很清楚上面的註解十分的混亂,有的地方描述確定有誤,也沒能力三言兩語把一個經典框架描述得清楚,敬請諒解,與君共勉。web