React中Redux核心原理深入分析
一、Redux是什麼
眾所周知,Redux最早運用於React框架中,是一個全局狀態管理器。Redux解決瞭在開發過程中數據無限層層傳遞而引發的一系列問題,因此我們有必要來瞭解一下Redux到底是如何實現的?
二、Redux的核心思想
Redux主要分為幾個部分:dispatch、reducer、state。
我們著重看下dispatch,該方法是Redux流程的第一步,在用戶界面中通過執行dispatch,傳入相對應的action對象參數,action是一個描述類型的對象,緊接著執行reducer,最後整體返回一個store對象,我們來看下這部分的源碼:
// 主函數createStore // 返回一個store對象 export default function createStore(reducer, preloadedState, enhancer) { // 增強器 if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) } 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 let isDispatching = false // 獲取最終的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.' ) } return currentState } // dispatch // 參數action function dispatch(action) { // 校驗傳入的action // action必須是個對象,否則拋出錯誤信息 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } // 檢驗action對象的必要屬性 // type屬性是action對象必要的屬性 // 如果傳入的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 // 執行傳入的reducer函數 // 返回state,給currentState賦值 currentState = currentReducer(currentState, action) } finally { // 一個dispatch執行完,還原狀態 isDispatching = false } // 執行訂閱函數隊列 // dispatch執行的同時會一並執行訂閱隊列 const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } // 返回action return action } // 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. // 默認執行一次dispatch,做初始化 dispatch({ type: ActionTypes.INIT }) // 返回一個store對象 return { dispatch, subscribe, getState, ... } }
通過源碼我們可以基本清楚,通過執行createStore方法,最終會返回一個store對象,該對象主要暴露幾個屬性,我們主要關註比較常用的:dispatch、getState、getState,看下實際用例:參考React實戰視頻講解:進入學習
import createStore from 'redux' // 創建一個reducer function reducer(state={}, action) { switch(action.type) { case 'TEST': return { ...state, test: 'test success' } } } // 返回store const store = createStore(reducer, initState={}) // 執行dispatch store.dispatch({ type: 'TEST' }) const state = store.getState() // 返回 {test: 'TEST'}
三、Redux中間件原理
接下來我們來探討Redux的另一個重要組成部分—中間件。什麼是Redux的中間件?Redux中間件其實是通過重寫createStore來增強和擴展原來的dispatch方法,使其能夠在執行dispatch的同時可以同步執行其它方法,比如redux-thunk就是一個處理異步的中間件:
function createThunkMiddleware(extraArgument) { // 中間件規定格式 // 閉包返回三層嵌套 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;
下載瞭中間件,那麼我們來看下如何使用中間件:
import createStore, {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->applyMiddleWare} from 'reduximport reduxThunk from 'redux-thunk'// 創建一個reducerfunction reducer(state={}, action) { switch(action.type) { case 'TEST': return { ...state, test: 'test success' } }}// 返回store// 中間件作為applyMiddleWare的參數傳入createStoreconst store = createStore(reducer, initState={},applyMiddleWare(reduxThunk))
我們會發現,中間件的使用方式是用applyMiddleWare把中間件作為參數傳入createStore中,那麼applyMiddleWare是如何實現的?在這之前我們先看下createStore方法的第三個參數是什麼,我們回看下createStore源碼:
export default function createStore(reducer, preloadedState, enhancer) { ... // 增強器 // 第三個參數是enhancer,也就是我們傳入的applyMiddleWare if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } // 在這裡return瞭enhancer結果 // 傳入瞭createStore,reducer,preloadedState // 實際上是重寫瞭createStore return enhancer(createStore)(reducer, preloadedState) } ... }
看完瞭enhancer的實際作用,我們可以弄清楚applyMiddleWare的實現原理,請看源碼:
import compose from './compose' // 傳入middlewares中間件 export default function applyMiddleware(...middlewares) { // 閉包嵌套返回2個方法 return createStore => (...args) => { // 返回store 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.' ) } // 返回一個對象 // 包含getState方法和dispatch方法 const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) // 返回一個全新的dispatch方法,不污染原來的dispatch } // 執行中間件第一層方法 // 回顧下中間的格式:({getState, dispatch}) => next => action => next(action) // 這裡會比較繞 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 返回一個中間件的函數集合[next => action => next(action), next => action => next(action)] // 使用compose聚合chain函數集合 // 返回新的dispatch dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
這裡可能會讓人很疑惑,不大清楚的童鞋可以先看下中間件的規范寫法,這裡還有一個重要的函數compose,我們來看下compose怎麼處理chain函數集合的,請看源碼:
/** * 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))). */ // 傳入聚合函數集合 // 集合為:[next => action => next(action), next => action => next(action)] // 返回一個新的函數: (arg) => arg export default function compose(...funcs) { // 判斷如果沒有則返回一個新函數 // 可以聯想一下dispatch的定義 // function dispatch(action) { ... return action } if (funcs.length === 0) { return arg => arg } // 判斷如果隻有一個中間件,則直接返回第一個 if (funcs.length === 1) { return funcs[0] } // 這裡用瞭reduce函數 // 把後一個的中間件的結果當成參數傳遞給下一個中間件 // 函數列表的每個函數執行後返回的還是一個函數:action => next(action) // 這個函數就是新的dispatch // 最後返回函數:(...args) => action => args(action) return funcs.reduce((a, b) => (...args) => a(b(...args))) }
compose的源碼及其簡潔,但是很精髓,幾乎是整個中間件最出彩的地方。通過reduce把每個中間件都執行一遍,並且是通過管道式的傳輸,把每個中間件的返回結果當成參數傳遞給下一個中間件,實現瞭剝洋蔥式的中間件模式。這裡比較難理解,新手可以先寫幾個簡單的中間件,然後再去慢慢理解為什麼要這麼處理,理解後就會知道這段代碼有多簡潔瞭。
四、手寫一個Redux
源碼解析完瞭,我們來簡單實現一個redux。
createStore
// 判斷值是否是對象類型 function isPlainObject(obj) { if(!obj) { reutrn false } return Object.prototype.toString.call(obj) === '[object, Object]' } export default createStore(reducer, enhancer) { // 先判斷有沒有傳入中間件 // 有則之間返回 if(typeof enhancer !== 'undefined') { // 必需是個函數 // 因為需要傳參 if(typeof enhancer !== 'function') { return } return enhancer(createStore)(reducer) } let state = {} // 初始化state let listeners = [] // 發佈訂閱函數隊列 // 定義getState 函數 function getState() { // 直接返回state return state } // 定義dispatch 函數 function dispatch(action) { try{ // 執行reducer, 返回state state = reducer(state, action) }catch(e) { console.log('dispatch error: 'e) } // 訂閱 listeners.forEach(listener => listener()) // 返回action return action } // 定義subscribe 函數 function subscribe(listener) { if(!listener) { return } // 必需是回掉函數 // 因為需要在dispatch裡執行 if(typeof listener !== 'function') { return } Listeners.push(listener) } // 返回對象:包含getState, dispatch, subscribe 三個方法 return { getState, dispatch, subscribe } }
compose
function compose(...funs) { if(!funs) { return arg => arg } if(funs.length === 1) { return funs[0] } // 遍歷傳入函數,返回一個新函數 return funs.reduce((a,b) => (...args) => a(b(...args))) }
applyMiddleWare
import compose from './compose' function applyMiddleWare(...middlewares) { return createStore => reducer => { // 先返回一個store const store = createStore(reducer) // 創建middleApi const middleApi = { getState: store.getState, dispatch: (...args) => store.dispatch(...args) // 返回一個新的dispatch } // 註入middleApi // 並返回函數集合 const chain = middlewares.map(middleWare => middleWare(middleApi)) // 通過compose函數,執行所有中間件,並返回一個新的dispatch const dispatch = compose(...chain)(store.dispatch) // 返回store對象 return { getState: store.getState, dispatch } } }
logger中間件
function logger({getState, dispatch}) { return function(next) { return function(action) { console.log('prev') next(action) console.log('done') } } }
測試
import createStore from './myCreateStore' import applyMiddleWare from './myApplyMiddleWare' import logger from './logger' // 創建reducer function reducer(state={}, action) { switch(action.type) { case 'TEST': return { ...state, test: 'test success' } } } // 引入中間件 const middleware = applyMiddleWare(logger) const store = createStore(reducer, middleware) // 返回{getState, dispatch}
總結
至此一個完整的redux我們就已經分析完瞭,個人認為中間件的compose這裡是比較不好理解的點,但是隻要明白中間件主要要解決的是增強dispatch函數,就可以順著這個思路去理解。接著再試著寫幾個中間件,進一步理解為什麼中間件的格式需要返回嵌套的三層函數,明白瞭這兩個點,redux的原理也就基本能夠明白瞭,有問題歡迎在評論中指出。
到此這篇關於React中Redux核心原理深入分析的文章就介紹到這瞭,更多相關React Redux內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 詳解JavaScript狀態容器Redux
- 詳解React 和 Redux的關系
- React Redux使用配置詳解
- react.js框架Redux基礎案例詳解
- React路由中的redux和redux知識點拓展