React中Redux核心原理深入分析
一、Redux是什么
眾所周知,Redux最早運(yùn)用于React框架中,是一個(gè)全局狀態(tài)管理器。Redux解決了在開發(fā)過程中數(shù)據(jù)無限層層傳遞而引發(fā)的一系列問題,因此我們有必要來了解一下Redux到底是如何實(shí)現(xiàn)的?
二、Redux的核心思想
Redux主要分為幾個(gè)部分:dispatch、reducer、state。
我們著重看下dispatch,該方法是Redux流程的第一步,在用戶界面中通過執(zhí)行dispatch,傳入相對(duì)應(yīng)的action對(duì)象參數(shù),action是一個(gè)描述類型的對(duì)象,緊接著執(zhí)行reducer,最后整體返回一個(gè)store對(duì)象,我們來看下這部分的源碼:
// 主函數(shù)createStore // 返回一個(gè)store對(duì)象 export default function createStore(reducer, preloadedState, enhancer) { // 增強(qiáng)器 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 // 參數(shù)action function dispatch(action) { // 校驗(yàn)傳入的action // action必須是個(gè)對(duì)象,否則拋出錯(cuò)誤信息 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } // 檢驗(yàn)action對(duì)象的必要屬性 // type屬性是action對(duì)象必要的屬性 // 如果傳入的action沒有type屬性,則拋出錯(cuò)誤信息 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 // 執(zhí)行傳入的reducer函數(shù) // 返回state,給currentState賦值 currentState = currentReducer(currentState, action) } finally { // 一個(gè)dispatch執(zhí)行完,還原狀態(tài) isDispatching = false } // 執(zhí)行訂閱函數(shù)隊(duì)列 // dispatch執(zhí)行的同時(shí)會(huì)一并執(zhí)行訂閱隊(duì)列 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. // 默認(rèn)執(zhí)行一次dispatch,做初始化 dispatch({ type: ActionTypes.INIT }) // 返回一個(gè)store對(duì)象 return { dispatch, subscribe, getState, ... } }
通過源碼我們可以基本清楚,通過執(zhí)行createStore方法,最終會(huì)返回一個(gè)store對(duì)象,該對(duì)象主要暴露幾個(gè)屬性,我們主要關(guān)注比較常用的:dispatch、getState、getState,看下實(shí)際用例:參考React實(shí)戰(zhàn)視頻講解:進(jìn)入學(xué)習(xí)
import createStore from 'redux' // 創(chuàng)建一個(gè)reducer function reducer(state={}, action) { switch(action.type) { case 'TEST': return { ...state, test: 'test success' } } } // 返回store const store = createStore(reducer, initState={}) // 執(zhí)行dispatch store.dispatch({ type: 'TEST' }) const state = store.getState() // 返回 {test: 'TEST'}
三、Redux中間件原理
接下來我們來探討Redux的另一個(gè)重要組成部分—中間件。什么是Redux的中間件?Redux中間件其實(shí)是通過重寫createStore來增強(qiáng)和擴(kuò)展原來的dispatch方法,使其能夠在執(zhí)行dispatch的同時(shí)可以同步執(zhí)行其它方法,比如redux-thunk就是一個(gè)處理異步的中間件:
function createThunkMiddleware(extraArgument) { // 中間件規(guī)定格式 // 閉包返回三層嵌套 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'// 創(chuàng)建一個(gè)reducerfunction reducer(state={}, action) { switch(action.type) { case 'TEST': return { ...state, test: 'test success' } }}// 返回store// 中間件作為applyMiddleWare的參數(shù)傳入createStoreconst store = createStore(reducer, initState={},applyMiddleWare(reduxThunk))
我們會(huì)發(fā)現(xiàn),中間件的使用方式是用applyMiddleWare把中間件作為參數(shù)傳入createStore中,那么applyMiddleWare是如何實(shí)現(xiàn)的?在這之前我們先看下createStore方法的第三個(gè)參數(shù)是什么,我們回看下createStore源碼:
export default function createStore(reducer, preloadedState, enhancer) { ... // 增強(qiáng)器 // 第三個(gè)參數(shù)是enhancer,也就是我們傳入的applyMiddleWare if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } // 在這里return了enhancer結(jié)果 // 傳入了createStore,reducer,preloadedState // 實(shí)際上是重寫了createStore return enhancer(createStore)(reducer, preloadedState) } ... }
看完了enhancer的實(shí)際作用,我們可以弄清楚applyMiddleWare的實(shí)現(xiàn)原理,請(qǐng)看源碼:
import compose from './compose' // 傳入middlewares中間件 export default function applyMiddleware(...middlewares) { // 閉包嵌套返回2個(gè)方法 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.' ) } // 返回一個(gè)對(duì)象 // 包含getState方法和dispatch方法 const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) // 返回一個(gè)全新的dispatch方法,不污染原來的dispatch } // 執(zhí)行中間件第一層方法 // 回顧下中間的格式:({getState, dispatch}) => next => action => next(action) // 這里會(huì)比較繞 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 返回一個(gè)中間件的函數(shù)集合[next => action => next(action), next => action => next(action)] // 使用compose聚合chain函數(shù)集合 // 返回新的dispatch dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
這里可能會(huì)讓人很疑惑,不大清楚的童鞋可以先看下中間件的規(guī)范寫法,這里還有一個(gè)重要的函數(shù)compose,我們來看下compose怎么處理chain函數(shù)集合的,請(qǐng)看源碼:
/** * 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))). */ // 傳入聚合函數(shù)集合 // 集合為:[next => action => next(action), next => action => next(action)] // 返回一個(gè)新的函數(shù): (arg) => arg export default function compose(...funcs) { // 判斷如果沒有則返回一個(gè)新函數(shù) // 可以聯(lián)想一下dispatch的定義 // function dispatch(action) { ... return action } if (funcs.length === 0) { return arg => arg } // 判斷如果只有一個(gè)中間件,則直接返回第一個(gè) if (funcs.length === 1) { return funcs[0] } // 這里用了reduce函數(shù) // 把后一個(gè)的中間件的結(jié)果當(dāng)成參數(shù)傳遞給下一個(gè)中間件 // 函數(shù)列表的每個(gè)函數(shù)執(zhí)行后返回的還是一個(gè)函數(shù):action => next(action) // 這個(gè)函數(shù)就是新的dispatch // 最后返回函數(shù):(...args) => action => args(action) return funcs.reduce((a, b) => (...args) => a(b(...args))) }
compose的源碼及其簡(jiǎn)潔,但是很精髓,幾乎是整個(gè)中間件最出彩的地方。通過reduce把每個(gè)中間件都執(zhí)行一遍,并且是通過管道式的傳輸,把每個(gè)中間件的返回結(jié)果當(dāng)成參數(shù)傳遞給下一個(gè)中間件,實(shí)現(xiàn)了剝洋蔥式的中間件模式。這里比較難理解,新手可以先寫幾個(gè)簡(jiǎn)單的中間件,然后再去慢慢理解為什么要這么處理,理解后就會(huì)知道這段代碼有多簡(jiǎn)潔了。
四、手寫一個(gè)Redux
源碼解析完了,我們來簡(jiǎn)單實(shí)現(xiàn)一個(gè)redux。
createStore
// 判斷值是否是對(duì)象類型 function isPlainObject(obj) { if(!obj) { reutrn false } return Object.prototype.toString.call(obj) === '[object, Object]' } export default createStore(reducer, enhancer) { // 先判斷有沒有傳入中間件 // 有則之間返回 if(typeof enhancer !== 'undefined') { // 必需是個(gè)函數(shù) // 因?yàn)樾枰獋鲄? if(typeof enhancer !== 'function') { return } return enhancer(createStore)(reducer) } let state = {} // 初始化state let listeners = [] // 發(fā)布訂閱函數(shù)隊(duì)列 // 定義getState 函數(shù) function getState() { // 直接返回state return state } // 定義dispatch 函數(shù) function dispatch(action) { try{ // 執(zhí)行reducer, 返回state state = reducer(state, action) }catch(e) { console.log('dispatch error: 'e) } // 訂閱 listeners.forEach(listener => listener()) // 返回action return action } // 定義subscribe 函數(shù) function subscribe(listener) { if(!listener) { return } // 必需是回掉函數(shù) // 因?yàn)樾枰赿ispatch里執(zhí)行 if(typeof listener !== 'function') { return } Listeners.push(listener) } // 返回對(duì)象:包含getState, dispatch, subscribe 三個(gè)方法 return { getState, dispatch, subscribe } }
compose
function compose(...funs) { if(!funs) { return arg => arg } if(funs.length === 1) { return funs[0] } // 遍歷傳入函數(shù),返回一個(gè)新函數(shù) return funs.reduce((a,b) => (...args) => a(b(...args))) }
applyMiddleWare
import compose from './compose' function applyMiddleWare(...middlewares) { return createStore => reducer => { // 先返回一個(gè)store const store = createStore(reducer) // 創(chuàng)建middleApi const middleApi = { getState: store.getState, dispatch: (...args) => store.dispatch(...args) // 返回一個(gè)新的dispatch } // 注入middleApi // 并返回函數(shù)集合 const chain = middlewares.map(middleWare => middleWare(middleApi)) // 通過compose函數(shù),執(zhí)行所有中間件,并返回一個(gè)新的dispatch const dispatch = compose(...chain)(store.dispatch) // 返回store對(duì)象 return { getState: store.getState, dispatch } } }
logger中間件
function logger({getState, dispatch}) { return function(next) { return function(action) { console.log('prev') next(action) console.log('done') } } }
測(cè)試
import createStore from './myCreateStore' import applyMiddleWare from './myApplyMiddleWare' import logger from './logger' // 創(chuàng)建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}
總結(jié)
至此一個(gè)完整的redux我們就已經(jīng)分析完了,個(gè)人認(rèn)為中間件的compose這里是比較不好理解的點(diǎn),但是只要明白中間件主要要解決的是增強(qiáng)dispatch函數(shù),就可以順著這個(gè)思路去理解。接著再試著寫幾個(gè)中間件,進(jìn)一步理解為什么中間件的格式需要返回嵌套的三層函數(shù),明白了這兩個(gè)點(diǎn),redux的原理也就基本能夠明白了,有問題歡迎在評(píng)論中指出。
到此這篇關(guān)于React中Redux核心原理深入分析的文章就介紹到這了,更多相關(guān)React Redux內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React?useEffect異步操作常見問題小結(jié)
本文主要介紹了React?useEffect異步操作常見問題小結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06React中的路由嵌套和手動(dòng)實(shí)現(xiàn)路由跳轉(zhuǎn)的方式詳解
這篇文章主要介紹了React中的路由嵌套和手動(dòng)實(shí)現(xiàn)路由跳轉(zhuǎn)的方式,手動(dòng)路由的跳轉(zhuǎn),主要是通過Link或者NavLink進(jìn)行跳轉(zhuǎn)的,實(shí)際上我們也可以通JavaScript代碼進(jìn)行跳轉(zhuǎn),需要的朋友可以參考下2022-11-11react 原生實(shí)現(xiàn)頭像滾動(dòng)播放的示例
這篇文章主要介紹了react 原生實(shí)現(xiàn)頭像滾動(dòng)播放的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04從零開始最小實(shí)現(xiàn)react服務(wù)器渲染詳解
這篇文章主要介紹了從零開始最小實(shí)現(xiàn)react服務(wù)器渲染詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-01-01React使用useEffect解決setState副作用詳解
這篇文章主要為大家介紹了React使用useEffect解決setState副作用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10深入理解react-router 路由的實(shí)現(xiàn)原理
這篇文章主要介紹了深入理解react-router 路由的實(shí)現(xiàn)原理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09