摘要:接下來(lái)筆者就從源碼中探尋是如何實(shí)現(xiàn)的。其實(shí)很簡(jiǎn)單,可以簡(jiǎn)單理解為一個(gè)約束了特定規(guī)則并且包括了一些特殊概念的的發(fā)布訂閱器。新舊中存在的任何都將收到先前的狀態(tài)。這有效地使用來(lái)自舊狀態(tài)樹(shù)的任何相關(guān)數(shù)據(jù)填充新?tīng)顟B(tài)樹(shù)。
Redux是當(dāng)今比較流行的狀態(tài)管理庫(kù),它不依賴(lài)于任何的框架,并且配合著react-redux的使用,Redux在很多公司的React項(xiàng)目中起到了舉足輕重的作用。接下來(lái)筆者就從源碼中探尋Redux是如何實(shí)現(xiàn)的。
注意:本文不去過(guò)多的講解Redux的使用方法,更多的使用方法和最佳實(shí)踐請(qǐng)移步Redux官網(wǎng)。源碼之前 基礎(chǔ)概念
隨著我們項(xiàng)目的復(fù)雜,項(xiàng)目中的狀態(tài)就變得難以維護(hù)起來(lái),這些狀態(tài)在什么時(shí)候,處于什么原因,怎樣變化的我們就很難去控制。因此我們考慮在項(xiàng)目中引入諸如Redux、Mobx這樣的狀態(tài)管理工具。
Redux其實(shí)很簡(jiǎn)單,可以簡(jiǎn)單理解為一個(gè)約束了特定規(guī)則并且包括了一些特殊概念的的發(fā)布訂閱器。
在Redux中,我們用一個(gè)store來(lái)管理一個(gè)一個(gè)的state。當(dāng)我們想要去修改一個(gè)state的時(shí)候,我們需要去發(fā)起一個(gè)action,這個(gè)action告訴Redux發(fā)生了哪個(gè)動(dòng)作,但是action不能夠去直接修改store里頭的state,他需要借助reducer來(lái)描述這個(gè)行為,reducer接受state和action,來(lái)返回新的state。
三大原則在Redux中有三大原則:
單一數(shù)據(jù)源:所有的state都存儲(chǔ)在一個(gè)對(duì)象中,并且這個(gè)對(duì)象只存在于唯一的store中;
state只讀性:唯一改變state的方法就是去觸發(fā)一個(gè)action,action用來(lái)描述發(fā)生了哪個(gè)行為;
使用純函數(shù)來(lái)執(zhí)行修改:reducer描述了action如何去修改state,reducer必須是一個(gè)純函數(shù),同樣的輸入必須有同樣的輸出;
剖析源碼 項(xiàng)目結(jié)構(gòu)拋去一些項(xiàng)目的配置文件和其他,Redux的源碼其實(shí)很少很簡(jiǎn)單:
index.js:入口文件,導(dǎo)出另外幾個(gè)核心函數(shù);
createStore.js:store相關(guān)的核心代碼邏輯,本質(zhì)是一個(gè)發(fā)布訂閱器;
combineReducers.js:用來(lái)合并多個(gè)reducer到一個(gè)root reducer的相關(guān)邏輯;
bindActionCreators.js:用來(lái)自動(dòng)dispatch的一個(gè)方法;
applyMiddleware.js:用來(lái)處理使用的中間件;
compose.js:導(dǎo)出一個(gè)通過(guò)從右到左組合參數(shù)函數(shù)獲得的函數(shù);
utils:兩個(gè)個(gè)工具函數(shù)和一個(gè)系統(tǒng)注冊(cè)的actionType;
從createStore來(lái)講一個(gè)store的創(chuàng)建首先我們先通過(guò)createStore函數(shù)的入?yún)⒑头祷刂祦?lái)簡(jiǎn)要理解它的功能:
export default function createStore(reducer, preloadedState, enhancer) { // ... return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
createStore接受三個(gè)參數(shù):
reducer:用來(lái)描述action如何改變state的方法,它給定當(dāng)前state和要處理的action,返回下一個(gè)state;
preloadedState:顧名思義就是初始化的state;
enhancer:可以直譯為增強(qiáng)器,用它來(lái)增強(qiáng)store的第三方功能,Redux附帶的唯一store增強(qiáng)器是applyMiddleware;
createStore返回一個(gè)對(duì)象,對(duì)象中包含使用store的基本函數(shù):
dispatch:用于action的分發(fā);
subscribe:訂閱器,他將會(huì)在每次action被dispatch的時(shí)候調(diào)用;
getState:獲取store中的state值;
replaceReducer:替換reducer的相關(guān)邏輯;
接下來(lái)我們來(lái)看看createStore的核心邏輯,這里我省略了一些簡(jiǎn)單的警告和判斷邏輯:
export default function createStore(reducer, preloadedState, enhancer) { // 判斷是不是傳入了過(guò)多的enhancer // ... // 如果不傳入preloadedState只傳入enhancer可以寫(xiě)成,const store = createStore(reducers, enhancer) // ... // 通過(guò)在增強(qiáng)器傳入createStore來(lái)增強(qiáng)store的基本功能,其他傳入的參數(shù)作為返回的高階函數(shù)參數(shù)傳入; 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.") } // 閉包內(nèi)的變量; // state作為內(nèi)部變量不對(duì)外暴露,保持“只讀”性,僅通過(guò)reducer去修改 let currentReducer = reducer let currentState = preloadedState // 確保我們所操作的listener列表不是原始的listener列表,僅是他的一個(gè)副本; let currentListeners = [] let nextListeners = currentListeners let isDispatching = false // 確保我們所操作的listener列表不是原始的listener列表,僅是他的一個(gè)副本; // 只有在dispatch的時(shí)候,才會(huì)去將currentListeners和nextListeners更新成一個(gè); function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } // 通過(guò)閉包返回了state,state僅可以通過(guò)此方法訪(fǎng)問(wèn); function getState() { // 判斷當(dāng)前是否在dispatch過(guò)程中 // ... return currentState } // Redux內(nèi)部的發(fā)布訂閱器 function subscribe(listener) { // 判斷l(xiāng)istener的合法性 // ... // 判斷當(dāng)前是否在dispatch過(guò)程中 // ... let isSubscribed = true // 復(fù)制一份當(dāng)前的listener副本 // 操作的都是副本而不是源數(shù)據(jù) ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } // 判斷當(dāng)前是否在dispatch過(guò)程中 // ... isSubscribed = false ensureCanMutateNextListeners() // 根據(jù)當(dāng)前l(fā)istener的索引從listener數(shù)組中刪除來(lái)實(shí)現(xiàn)取掉訂閱; const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } } function dispatch(action) { // 判斷action是不是一個(gè)普通對(duì)象; // ... // 判斷action的type是否合法 // ... // 判斷當(dāng)前是否在dispatch過(guò)程中 // ... try { isDispatching = true // 根據(jù)要觸發(fā)的action, 通過(guò)reducer來(lái)更新當(dāng)前的state; currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 通知listener執(zhí)行對(duì)應(yīng)的操作; const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action } // 替換reducer,修改state變化的邏輯 function replaceReducer(nextReducer) { if (typeof nextReducer !== "function") { throw new Error("Expected the nextReducer to be a function.") } currentReducer = nextReducer // 此操作對(duì)ActionTypes.INIT具有類(lèi)似的效果。 // 新舊rootReducer中存在的任何reducer都將收到先前的狀態(tài)。 // 這有效地使用來(lái)自舊狀態(tài)樹(shù)的任何相關(guān)數(shù)據(jù)填充新?tīng)顟B(tài)樹(shù)。 dispatch({ type: ActionTypes.REPLACE }) } function observable() { const outerSubscribe = subscribe return { // 任何對(duì)象都可以被用作observer,observer對(duì)象應(yīng)該有一個(gè)next方法 subscribe(observer) { if (typeof observer !== "object" || observer === null) { throw new TypeError("Expected the observer to be an object.") } function observeState() { if (observer.next) { observer.next(getState()) } } observeState() const unsubscribe = outerSubscribe(observeState) // 返回一個(gè)帶有unsubscribe方法的對(duì)象可以被用來(lái)在store中取消訂閱 return { unsubscribe } }, [$$observable]() { return this } } } // 創(chuàng)建store時(shí),將調(diào)度“INIT”操作,以便每個(gè)reducer返回其初始狀態(tài),以便state的初始化。 dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }從combineReducers談store的唯一性
僅靠上面的createStore其實(shí)已經(jīng)可以完成一個(gè)簡(jiǎn)單的狀態(tài)管理了,但是隨著業(yè)務(wù)體量的增大,state、action、reducer也會(huì)隨之增大,我們不可能把所有的東西都塞到一個(gè)reducer里,最好是劃分成不同的reducer來(lái)處理不同模塊的業(yè)務(wù)。
但是也不能創(chuàng)建多個(gè)store維護(hù)各自的reducer,這就違背了Redux的單一store原則。為此,Redux提供了combineReducers讓我們將按照業(yè)務(wù)模塊劃分的reducer合成一個(gè)rootReducer。
接下來(lái)我們看看combineReducers的源碼,這里也是去掉了一些錯(cuò)誤警告的代碼和一些錯(cuò)誤處理方法:
export default function combineReducers(reducers) { // 取出所有的reducer遍歷合并到一個(gè)對(duì)象中 const reducerKeys = Object.keys(reducers) const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] // 判斷未匹配的refucer // ... if (typeof reducers[key] === "function") { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) // 錯(cuò)誤處理的一些邏輯 // ... return function combination(state = {}, action) { // 錯(cuò)誤處理的一些邏輯 // ... let hasChanged = false const nextState = {} for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] // 對(duì)應(yīng)的reducer const reducer = finalReducers[key] // 根據(jù)指定的reducer找到對(duì)應(yīng)的state const previousStateForKey = state[key] // 執(zhí)行reducer, 返回當(dāng)前state const nextStateForKey = reducer(previousStateForKey, action) // nextStateForKey undefined的一些判斷 // ... // 整合每一個(gè)reducer對(duì)應(yīng)的state nextState[key] = nextStateForKey // 判斷新的state是不是同一引用, 以檢驗(yàn)reducer是不是純函數(shù) hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
其實(shí)到這里可以簡(jiǎn)單的看出combineReducers就是把多個(gè)reducer拉伸展開(kāi)到到一個(gè)對(duì)象里,同樣也把每一個(gè)reducer里的state拉伸到一個(gè)對(duì)象里。
從bindActionCreators談如何自動(dòng)dispatch現(xiàn)有的store每一次state的更新都需要手動(dòng)的dispatch每一個(gè)action,而我們其實(shí)更需要的是自動(dòng)的dispatch所有的action。這里就用到了bindActionCreators方法。
現(xiàn)在我們來(lái)看看bindActionCreators的源碼
function bindActionCreator(actionCreator, dispatch) { return function() { return dispatch(actionCreator.apply(this, arguments)) } } export default function bindActionCreators(actionCreators, dispatch) { // 返回綁定了this的actionCreator if (typeof actionCreators === "function") { return bindActionCreator(actionCreators, dispatch) } // actionCreators類(lèi)型判斷的錯(cuò)誤處理 // ... // 為每一個(gè)actionCreator綁定this const boundActionCreators = {} for (const key in actionCreators) { const actionCreator = actionCreators[key] if (typeof actionCreator === "function") { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }
其實(shí)我們?cè)趓eact項(xiàng)目中對(duì)這個(gè)方法是幾乎無(wú)感知的,因?yàn)槭窃趓eact-redux的connect中調(diào)用了這個(gè)方法來(lái)實(shí)現(xiàn)自動(dòng)dispatch action的,不然需要手動(dòng)去dispatch一個(gè)個(gè)action。
從compose談函數(shù)組合compose是Redux導(dǎo)出的一個(gè)方法,這方法就是利用了函數(shù)式的思想對(duì)函數(shù)進(jìn)行組合:
// 通過(guò)從右到左組合參數(shù)函數(shù)獲得的函數(shù)。例如,compose(f, g, h)與do(...args)=> f(g(h(... args)))相同。 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))) }從applyMiddleware談如何自定義dispatch
我們的action會(huì)出現(xiàn)同步的場(chǎng)景,當(dāng)然也會(huì)出現(xiàn)異步的場(chǎng)景,在這兩種場(chǎng)景下dispacth的執(zhí)行時(shí)機(jī)是不同的,在Redux中,可以使用middleware來(lái)對(duì)dispatch進(jìn)行改造,下面我們來(lái)看看applyMiddleware的實(shí)現(xiàn):
import compose from "./compose" 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." ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 通過(guò)從右到左組合參數(shù)函數(shù)獲得的函數(shù)。例如,compose(f, g, h)與do(...args)=> f(g(h(... args)))相同。 // 對(duì)dispatch改造 dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }結(jié)語(yǔ)
到此,Redux源碼的部分就分析完了,但是在具體和React結(jié)合的時(shí)候還需要用到react-redux,下一篇文章,我將深入到react-redux的源碼學(xué)習(xí),來(lái)探索在react中,我們?nèi)绾稳ナ褂肦edux。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/105663.html
摘要:前言一直混跡社區(qū)突然發(fā)現(xiàn)自己收藏了不少好文但是管理起來(lái)有點(diǎn)混亂所以將前端主流技術(shù)做了一個(gè)書(shū)簽整理不求最多最全但求最實(shí)用。 前言 一直混跡社區(qū),突然發(fā)現(xiàn)自己收藏了不少好文但是管理起來(lái)有點(diǎn)混亂; 所以將前端主流技術(shù)做了一個(gè)書(shū)簽整理,不求最多最全,但求最實(shí)用。 書(shū)簽源碼 書(shū)簽導(dǎo)入瀏覽器效果截圖showImg(https://segmentfault.com/img/bVbg41b?w=107...
摘要:的綁定庫(kù)包含了容器組件和展示組件相分離的開(kāi)發(fā)思想。明智的做法是只在最頂層組件如路由操作里使用。其余內(nèi)部組件僅僅是展示性的,所有數(shù)據(jù)都通過(guò)傳入。 Redux 的 React 綁定庫(kù)包含了 容器組件和展示組件相分離 的開(kāi)發(fā)思想。明智的做法是只在最頂層組件(如路由操作)里使用 Redux。其余內(nèi)部組件僅僅是展示性的,所有數(shù)據(jù)都通過(guò) props 傳入。 那么為什么需要容器組件和展示組件相分離呢...
摘要:最后,狀態(tài)管理與同構(gòu)實(shí)戰(zhàn)這本書(shū)由我和前端知名技術(shù)大佬顏海鏡合力打磨,凝結(jié)了我們?cè)趯W(xué)習(xí)實(shí)踐框架過(guò)程中的積累和心得。 對(duì)于前端資訊比較敏感的同學(xué),可能這兩天已經(jīng)聽(tīng)說(shuō)了 GoogleChromeLabs/quicklink這個(gè)項(xiàng)目:它由 Google 公司著名開(kāi)發(fā)者 Addy Osmani 發(fā)起,實(shí)現(xiàn)了:在空閑時(shí)間預(yù)獲取頁(yè)面可視區(qū)域內(nèi)的鏈接,加快后續(xù)加載速度。如果你沒(méi)有聽(tīng)說(shuō)過(guò) Addy Os...
閱讀 563·2021-10-09 09:44
閱讀 2256·2021-09-02 15:41
閱讀 3619·2019-08-30 15:53
閱讀 1882·2019-08-30 15:44
閱讀 1346·2019-08-30 13:10
閱讀 1266·2019-08-30 11:25
閱讀 1539·2019-08-30 10:51
閱讀 3425·2019-08-30 10:49