做爲前端狀態管理器,這個比較跨時代的工具庫redux有不少實現和思想值得咱們思考。在深刻源碼以前,咱們能夠相關注下一些常見問題,這樣帶着問題去看實現,也能更加清晰的瞭解。
html
大概看了下主要有這麼幾個:前端
有關acton,reducer相關的部分能夠看我前面的文章。咱們主要關注針對store和中間件相關的部分來解讀。react
做爲維護和管理數據的部分,store在redux中的做用十分重要。在action發出指令,reduxer進行數據更新以後,監聽數據變化和同步數據更新的操做都要藉助store來實現。redux
首先看下createStore的使用,即常見的就是接受通過combineReducers處理以後的reducer和初始的state數組
import reducer from './reducers' const store = createStore(reducer,initialState)
此外還能夠接受第三個參數enhancer(加強器,通常就是applyMiddleware)app
/** * 建立管理state 樹的Redux store * 應用中只能存在一個store,爲了區分不一樣action對應的reducer, * 能夠經過combineReducers來關聯不一樣的reducer * @param {Function} reducer combineReducers關聯以後的reducer * @param {Object} preloadedState 初始state * @param {Function} enhancer 能夠加強store功能的函數,例如中間件等。惟一適合 * @returns 返回一個Store 以維護state和監聽變化 */ export default function createStore(reducer, preloadedState, enhancer) { // 若是第二個參數爲func,redux認爲忽略了初始state,而是 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { // enhancer加強劑,即利用中間件等來加強redux能力 enhancer = preloadedState preloadedState = undefined } // 返回具備dispatch等屬性的對象 即store return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
按照通常的執行順序,咱們先看下對於參數的處理(平時你們也是同樣,一個函數,執行以前儘可能判斷入參是否符合預期,避免直接處理形成的錯誤)異步
對於三個參數,後兩個是非必填的,但若是第二個參數是function,reduxe認爲其實encher,否則初始狀態是個函數不符合redux的預期,只能這樣處理了。函數
// 若是第二個參數爲func,redux認爲忽略了初始state,而是 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { // enhancer加強劑,即利用中間件等來加強redux能力 enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } // 對於存在的enhancer,高階函數函數的用法, // 接受createStore返回一個增長功能以後的函數,而後再傳入相關reducer獲得store。 return enhancer(createStore)(reducer, preloadedState) } if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } // 一切符合預期,沒有 enhancer,那麼初始賦值 let currentReducer = reducer let currentState = preloadedState let currentListeners = [] // 監聽隊列 let nextListeners = currentListeners // dispatch標識 let isDispatching = false // 初始狀態更新以後,聲明init狀態完成。 dispatch({ type: ActionTypes.INIT })
dispatch的做用就是根據action,執行對應reducer以更新state。並執行監聽隊列。
下面就來看dispatch的用法和實現。
常見使用:工具
// redux要求 參數必須爲純對象 dispatch({ type: ActionTypes.INIT })
那麼對於純對象,redux作了些什麼呢post
/** * 通知方法,參數爲純的js對象,標明更新內容 * @param {Object} action */ function dispatch(action) { // 是否知足純淨對象 if (!isPlainObject(action)) { throw new Error( '省略' ) } // 必須的type是否存在 if (typeof action.type === 'undefined') { throw new Error( '省略' ) } // 判斷是否處於某個action的dispatch中,你們一塊兒dispatch可能死循環 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { // 開始dispatch,加鎖,標明狀態 isDispatching = true // 將當前狀態和更新action,傳給當前reducer處理 // 這裏能夠回想下咱們reducer中的兩個參數,state和action 對應的是什麼 /** * const todos = (state = [], action) => { */ currentState = currentReducer(currentState, action) } finally { // 有異常,鎖置爲false,不影響別的dispatch isDispatching = false } // 執行dispatch,而且更新當前監聽隊列爲 最新隊列 const listeners = (currentListeners = nextListeners) // 依次執行,監聽器 for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
createStore初始化完成以後會執行dispatch({ type: ActionTypes.INIT }),此時執行初始化操做。
咱們要關注下currentState的計算,
將currentState,action傳給reducer處理,而後更新currentState。
針對初始化來講currentState其實就是initState:
// 初始化狀態 let currentState = preloadedState /****省略***/ // 這裏能夠回想下咱們reducer中的兩個參數,state和action對應的值 currentState = currentReducer(currentState, action)
reducer示例:
const todos = (state = [], action) => { switch (action.type) { case 'ADD_TODO': return [ ...state, { id: action.id, text: action.text, completed: false } ] }
getState就是得到store的state。這個比較簡單。當結合react-redux使用時,其會幫咱們進行操做。咱們就不用自行調用這個方法了,因此不要疑惑從哪裏獲取的state。
/** * 返回應用當前狀態 * 不過須要看下當前是否有更新正在進行,是的話則提示 */ function getState() { // 判斷是否isDispatching 中 if (isDispatching) { throw new Error('省略') } return currentState }
subscribe是比較重要的一個方法,用來供咱們監聽狀態的變化,以執行相關操做。
例如react-redux中的handleChange 會對是否pure組件及state進行對比,以提高渲染效率。
示例:
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
實現:
返回的是一個函數,能夠進行unsubscribe操做。
/** * 訂閱通知 */ function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } if (isDispatching) { throw new Error( '省略' ) } // 是否已經監聽過 let isSubscribed = true // 監聽隊列是否相同,區分開,操做nextListeners ensureCanMutateNextListeners() // 新增監聽事件 nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } if (isDispatching) { throw new Error( '省略' ) } // 註冊完成,能夠進行取消操做 isSubscribed = false // 保持事件隊列 同步 ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) // 刪除監聽事件 nextListeners.splice(index, 1) } }
這個開發比較少用,用於熱更新
// 用於reducer的熱替換,開發中通常不會直接使用 function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer // 更新值以後,進行dispatch。 dispatch({ type: ActionTypes.REPLACE }) }
到這裏createStore已經解析完成了,你們應該瞭解該方法到底作了些什麼操做吧。
簡單歸納一下就是:接收reducer和initState,返回一個store 對象。該對象提供了監聽、分發等功能,以實現數據的更新。
通過上面的解讀以後,對於redux的常規應用應該有所瞭解了。不過實際使用中可能會遇到些問題。
例如action要求是純對象,而咱們獲取數據通常是異步的,這就須要藉助redux-thunk這個中間件了。
actionCreater返回一個函數。以下:
export function func1() { return dispatch => { dispatch({ type:'test', data:'a' }) } }
在瞭解如何實現以前,須要先看下redux中間件的原理。
由於reducer更多的關注的是數據的操做,對於一些公共的方法,須要抽離出來,不過這些方法在什麼時候使用呢,redux爲咱們提供了中間件來知足需求。
redux 借鑑了 Koa裏 middleware 的思想,即鼎鼎大名的洋蔥模型。
不過這裏請求對應的是dispatch的過程。
每次dispatch的過程當中,都要依次將中間件執行一遍。
遇到阻塞或者操做完成,執行下箇中間件,直到執行完成,以便咱們事先日誌,監控、異常上報等功能。
那麼redux 又是如何支持中間件的呢。這就離不開applyMiddleware了。
這裏前面的
實現思想比較簡單,經過科裏化和compose,爲符合規範的中間件分配訪問dispatch和store的途徑,以便在不一樣階段來自定義數據更新。
例如異步操做,返回的不是對象,那麼就執行返回的函數,而後調用下一個中間件。等異步請求結束,再次dispatch 對應的action。
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.` ) } // 賦予每一箇中間件訪問store的能力。 const middlewareAPI = { getState: store.getState, // 箭頭函數保存dispatch,保證其的同步更新 dispatch: (...args) => dispatch(...args) } // 串聯中間件,並賦予每一箇中間件訪問dispatch的能力。 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 關聯dispatch與中間件,組合調用以後獲得相似下面的新對象 // dispatch = f1(f2(f3(store.dispatch)))); dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
這樣執行以後返回的,對象就是加強以後的store了。
redux中compose是柯里化函數的一個示例,目的是將函數串聯起來。
/** * 函數組合,科裏化的串聯 */ 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))) }
redux-thunk源碼,實現也很優雅,對於返回的function,將dispatch等參數傳遞進去,而後執行,等待回調異步完成再dispatch。對於正常對象則進行下一步。
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { // 每次dispatch的時候都會進行判斷,若是是個函數,那就執行函數,再也不進行下一步吧,這樣就避免了,函數不知足action要求的問題 if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
那麼實際使用時,在createStore時加入該中間件便可:
import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk'; const store = createStore( reducer, applyMiddleware({ ...middleware, thunk}) )
那麼到這裏對於redux的中間件 也就是問題2,我想你們也比較清楚了。
對於常見中間件能夠參考
redux中文文檔
深刻React技術棧
加上重讀redux源碼一和帶着問題看 react-redux 源碼實現總算將redux及react-redux重讀了一遍。可能有人會說道這些源碼,看完也會忘,有這個必要嗎。我感受分狀況來看,若是咱們只是使用,那麼看官方文檔就能夠了,當遇到某些疑問好像找不到貼切解釋的時候,不放一看。 此外也是學習大佬們的設計思路和實現方式,有的放矢才能開卷有益。