摘要:訂閱器不應該關注所有的變化,在訂閱器被調用之前,往往由于嵌套的導致發生多次的改變,我們應該保證所有的監聽都注冊在之前。
前言
用 React + Redux 已經一段時間了,記得剛開始用Redux 的時候感覺非常繞,總搞不起里面的關系,如果大家用一段時間Redux又看了它的源碼話,對你的理解會有很大的幫助??赐旰?,在回來看Redux,有一種 柳暗花明又一村 的感覺 ,.
源碼我分析的是用 es6 語法的源碼,大家看目錄結構,一共有 6 個問件。先說下各個文件大概功能。
applyMiddlewar.js 使用自定義的 middleware 來擴展 Redux
bindActionCreators.js 把 action creators 轉成擁有同名 keys 的對象,使用時可以直接調用
combineReducers.js 一個比較大的應用,需要對 reducer 函數 進行拆分,拆分后的每一塊獨立負責管理 state 的一部分
compose.js 從右到左來組合多個函數,函數編程中常用到
createStore.js 創建一個 Redux Store 來放所有的state
utils/warnimng.js 控制臺輸出一個警告,我們可以不用看
我會按每個模塊的重要性,去做分析,今天就先把 createStore .js 分享給大家.
createStore.js注釋很詳細,直接看注釋就可以了
// 導入 lodash ,判斷是否是普通(plain)對象 import isPlainObject from "lodash/isPlainObject" //導入 symbol 類型的 observable (symbol類型的屬性,是對象的私有屬性) import $$observable from "symbol-observable" /** *定義 Redux Action 的初始化 type * */ export var ActionTypes = { INIT: "@@redux/INIT" } /** * 創建一個Redux store來存放應用所有的 state。應用中有且只有一個store * * @param {Function} reducer 是一個函數,接收兩個參數,分別是當前的 state 樹和 * 要處理的 action,返回新的 state 樹 * * @param {any} 初始化時的state ,在應用中,你可以把服務端傳來經過處理后的 state *傳給它。如果你使用 combineReducers 創建 reducer,它必須是一個普通對象,與傳入 *的 keys 保持同樣的結構。否則,你可以自由傳入任何 reducer 可理解的內容。 * * @param {Function} enhancer 是一個組合的高階函數,返回一個強化過的 store creator . * 這與 middleware相似,它也允許你通過復合函數改變 store 接口。 * * @returns {Store} 返回一個對象,給外部提供 dispatch, getState, subscribe, replaceReducer, */ export default function createStore(reducer, preloadedState, enhancer) { //判斷 preloadedState 是一個函數并且 enhancer 是未定義 if (typeof preloadedState === "function" && typeof enhancer === "undefined") { enhancer = preloadedState // 把 preloadedState 賦值給 enhancer preloadedState = undefined // 把 preloadedState 賦值為 undefined } //判斷 enhancer 不是 undefined if (typeof enhancer !== "undefined") { //判斷 enhancer 不是一個函數 if (typeof enhancer !== "function") { //拋出一個異常 (enhancer 必須是一個函數) throw new Error("Expected the enhancer to be a function.") } //調用 enhancer ,返回一個新強化過的 store creator return enhancer(createStore)(reducer, preloadedState) } //判斷 reducer 不是一個函數 if (typeof reducer !== "function") { //拋出一個異常 (reducer 必須是一個函數) throw new Error("Expected the reducer to be a function.") } var currentReducer = reducer //把 reducer 賦值給 currentReducer var currentState = preloadedState //把 preloadedState 賦值給 currentState var currentListeners = [] //初始化 listeners 數組 var nextListeners = currentListeners//nextListeners 和 currentListeners 指向同一個引用 var isDispatching = false //標記正在進行dispatch /** * 保存一份訂閱快照 * @return {void} */ function ensureCanMutateNextListeners() { //判斷 nextListeners 和 currentListeners 是同一個引用 if (nextListeners === currentListeners) { //通過數組的 slice 方法,復制出一個 listeners ,賦值給 nextListeners nextListeners = currentListeners.slice() } } /** * 獲取 store 里的當前 state tree * * @returns {any} 返回應用中當前的state tree */ function getState() { //當前的state tree return currentState } /** * * 添加一個 listener . 當 dispatch action 的時候執行,這時 sate 已經發生了一些變化, * 你可以在 listener 函數調用 getState() 方法獲取當前的 state * * 你可以在 listener 改變的時候調用 dispatch ,要注意 * * 1. 訂閱器(subscriptions) 在每次 dispatch() 調用之前都會保存一份快照。 * 當你在正在調用監聽器(listener)的時候訂閱(subscribe)或者去掉訂閱(unsubscribe), * 對當前的 dispatch() 不會有任何影響。但是對于下一次的 dispatch(),無論嵌套與否, * 都會使用訂閱列表里最近的一次快照。 * * 2. 訂閱器不應該關注所有 state 的變化,在訂閱器被調用之前,往往由于嵌套的 dispatch() * 導致 state 發生多次的改變,我們應該保證所有的監聽都注冊在 dispath() 之前。 * * @param {Function} 要監聽的函數 * @returns {Function} 一個可以移除監聽的函數 */ function subscribe(listener) { //判斷 listener 不是一個函數 if (typeof listener !== "function") { //拋出一個異常 (listener 必須是一個函數) throw new Error("Expected listener to be a function.") } //標記有訂閱的 listener var isSubscribed = true //保存一份快照 ensureCanMutateNextListeners() //添加一個訂閱函數 nextListeners.push(listener) //返回一個取消訂閱的函數 return function unsubscribe() { //判斷沒有訂閱一個 listener if (!isSubscribed) { //調用 unsubscribe 方法的時候,直接 return return } //標記現在沒有一個訂閱的 listener isSubscribed = false //保存一下訂閱快照 ensureCanMutateNextListeners() //找到當前的 listener var index = nextListeners.indexOf(listener) //移除當前的 listener nextListeners.splice(index, 1) } } /** * dispath action。這是觸發 state 變化的惟一途徑。 * * @param {Object} 一個普通(plain)的對象,對象當中必須有 type 屬性 * * @returns {Object} 返回 dispatch 的 action * * 注意: 如果你要用自定義的中間件, 它可能包裝 `dispatch()` * 返回一些其它東西,如( Promise ) */ function dispatch(action) { //判斷 action 不是普通對象。也就是說該對象由 Object 構造函數創建 if (!isPlainObject(action)) { //拋出一個異常(actions 必須是一個普通對象. 或者用自定義的中間件來處理異步 actions) throw new Error( "Actions must be plain objects. " + "Use custom middleware for async actions." ) } // 判斷 action 對象的 type 屬性等于 undefined if (typeof action.type === "undefined") { //拋出一個異常 throw new Error( "Actions may not have an undefined "type" property. " + "Have you misspelled a constant?" ) } //判斷 dispahch 正在運行,Reducer在處理的時候又要執行 dispatch if (isDispatching) { //拋出一個異常(Reducer在處理的時候不能 dispatch action) throw new Error("Reducers may not dispatch actions.") } try { //標記 dispatch 正在運行 isDispatching = true //執行當前 Reducer 函數返回新的 state currentState = currentReducer(currentState, action) } finally { // (try catch) 最終會被執行的地方 //標記 dispatch 沒有在運行 isDispatching = false } //所有的的監聽函數賦值給 listeners var listeners = currentListeners = nextListeners //遍歷所有的監聽函數 for (var i = 0; i < listeners.length; i++) { // 執行每一個監聽函數 listeners[i]() } //返回傳入的 action 對象 return action } /** * 替換計算 state 的 reducer。 * * 這是一個高級 API。 * 只有在你需要實現代碼分隔,而且需要立即加載一些 reducer 的時候才可能會用到它。 * 在實現 Redux 熱加載機制的時候也可能會用到。 * * @param {Function} store 要用的下一個 reducer. * @returns {void} */ function replaceReducer(nextReducer) { //判斷 nextReducer 不是一個函數 if (typeof nextReducer !== "function") { //拋出一個異常 (nextReducer必須是一個函數) throw new Error("Expected the nextReducer to be a function.") } //當前傳入的 nextReducer 賦值給 currentReducer currentReducer = nextReducer //調用 dispatch 函數,傳入默認的 action dispatch({ type: ActionTypes.INIT }) } /** * 在 creatorStore 內部沒有看到此方法的調用 * (猜想 : 作者可能想用比較強大,活躍的 observable 庫替換現在的 publish subscribe) * * @returns {observable} 狀態改變的時候返回最小的 observable. * 想要了解跟多關于 observable 庫,建議看下 * https://github.com/zenparsing/es-observable (標準 es Observable) */ function observable() { //訂閱方法賦值給變量 outerSubscribe var outerSubscribe = subscribe return { /** * 這是一個最小的觀察訂閱方法 * * @param {Object} 觀察著的任何一個對象都可以作為一個 observer. * 觀察者應該有 `next` 方法 */ subscribe(observer) { //判斷 observer 是一個對象 if (typeof observer !== "object") { //拋出異常 throw new TypeError("Expected the observer to be an object.") } //獲取觀察著的狀態 function observeState() { if (observer.next) { observer.next(getState()) } } observeState() //返回一個取消訂閱的方法 var unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, //對象的私有屬性,暫時不知道有什么用途 [$$observable]() { return this } } } //reducer 返回其初始狀態 //初始化 store 里的 state tree dispatch({ type: ActionTypes.INIT }) //返回 store 暴漏出的接口 return { dispatch, //唯一一個可以改變 state 的哈按時 subscribe, //訂閱一個狀態改變后,要觸發的監聽函數 getState, // 獲取 store 里的 state replaceReducer, //Redux熱加載的時候可以替換 Reducer [$$observable]: observable //對象的私有屬性,供內部使用 } }結束語
代碼已經放在 githunb 上,大家可以查看 https://github.com/jiechud/redux-source-analyze , 如果對你有幫助,麻煩請 Star 一下吧.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88051.html
摘要:找工作之前看了很多面試題,復習資料,但是發現純看面試題是不行的,因為靠背的東西是記不牢的,需要知識成體系才可以,所以筆者整理了一份復習大綱,基本涵蓋了中高級工程師面試所必須知識點,希望可以通過此文幫助一些想換工作的朋友更好的復習,準備面試。 概述 都說金三銀四青銅五,這幾個月份是程序員最好的跳槽時間,筆者四月初也換了工作。找工作之前看了很多面試題,復習資料,但是發現純看面試題是不行的,因為靠...
摘要:在嚴格模式下調用函數則不影響默認綁定。回調函數丟失綁定是非常常見的。因為直接指定的綁定對象,稱之為顯示綁定。調用時強制把的綁定到上顯示綁定無法解決丟失綁定問題。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第三期,本周的主題是this全面解析,今天是第9天。 本計劃一共28期,每期重點攻克一個面試重...
摘要:層確保數據一致性和可靠性。保證集群的相關組件在同一時刻能夠達成一致,相當于集群的領導層,負責收集更新和發布集群信息。元數據服務器,跟蹤文件層次結構并存儲只供使用的元數據。啟迪云-高級開發工程師 ?侯玉彬前言上一次簡單的介紹Ceph的過去和未來的發展。這一節將詳細介紹Ceph的構件以及組件。Ceph存儲架構Ceph 存儲集群由幾個不同的daemon組成,每個daemon負責Ceph 的一個獨特...
摘要:入冬了,寒風呼嘯,白雪飄飄,此刻窩在家里學習應當是極好的。為了滿足大家的需求,小編火速為大家整理了史上最全的資料。 showImg(https://segmentfault.com/img/remote/1460000007586577?w=900&h=500); 入冬了,寒風呼嘯,白雪飄飄,此刻窩在家里學習應當是極好的。為了滿足大家的需求,小編火速為大家整理了史上最全的Docker資...
閱讀 3027·2021-11-02 14:40
閱讀 844·2019-08-30 15:53
閱讀 1265·2019-08-30 15:53
閱讀 3259·2019-08-30 13:53
閱讀 3305·2019-08-29 12:50
閱讀 1132·2019-08-26 13:49
閱讀 1864·2019-08-26 12:20
閱讀 3660·2019-08-26 11:33