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