摘要:源碼解析是最核心的模塊。比如,當我們需要使用中間件的時候,就會像第三個參數傳遞一個返回值是一個。后續的源碼解讀和測試例子可以關注源碼解讀倉庫
createStore源碼解析
createStore是redux最核心的模塊。這個模塊就是用于創建一個store對象,同時,對外暴露出dispatch,getState,subscribe和replaceReducer方法。(源碼中關于observable的部分可以忽略,這個是redux內部使用的。我們在開發中幾乎幾乎用不到)
先看一下這個模塊的基本結構:
依賴
lodash/isPlainObject
symbol-observable
對外輸出
dispatch
getState
subscribe
replaceReducer
[$$observable](幾乎不用)
redux源碼中使用的lodash/isPlainObject依賴。在IE6-8中性能很差,其實現方式和jQuery3.x的實現相似,在舊版本的IE中支持不了。最后會和大家一起探討。
源碼注釋
// 判斷是不是純粹對象的模塊({}) import isPlainObject from "lodash/isPlainObject" // 引入observable支持 import $$observable from "symbol-observable"
export const ActionTypes = { INIT: "@@redux/INIT" }
上面這個是redux內部使用的一個action。主要用于內部測試和渲染初始的state。記住,我們自己編寫action的時候,action.type不能為@@redux/INIT。因為,這個action會在redux的內部自動調用。比如,下面的搗蛋代碼:
import {createStore, combineReducers, applyMiddleware} from "../src" import logger from "redux-logger" const actionTypes = "@@redux/INIT" const reducers = (state = {}, action) => { switch(action.type) { case actionTypes: console.log("hello @@redux/INIT") return { "type": actionTypes } default: return state } } const store = createStore(reducers, applyMiddleware(logger)) console.log("*************************************") console.log(store.getState()) //會渲染為 {"type": "@@redux/INIT"} console.log("*************************************")
下面就是createStore方法的實現:
export default function createStore(reducer, preloadedState, enhancer){ //...初始條件的判斷和設定 function getState() { // getState方法的實現 } function subscribe() { // subscribe方法的實現 } function dispatch() { // dispatch方法的實現 } function replaceReducer() { // replaceReducer方法的實現 } function observable() { // observable方法的實現 } // store被創建后,自動分發一個"INIT" action。渲染出初始化的state樹。 dispatch({ type: ActionTypes.INIT }) }
下面就來一點點分析每一行代碼到底做什么:
if (typeof preloadedState === "function" && typeof enhancer === "undefined") { enhancer = preloadedState preloadedState = undefined }
在調用createStore的方法的時候,可以傳遞三個參數createStore(reducer, preloadedState, enhancer)。其中,各參數屬性如下:
reducer必需參數,function類型
preloadedState可選參數,object類型
enhancer可選參數,function類型
在平常的使用中,我們一般會省略第二個參數。比如,當我們需要使用redux中間件的時候,就會像第三個參數傳遞一個applyMiddleware()[返回值是一個function]。如果,我們沒有初始狀態,則會省略第二個參數。這個時候,我們的函數調用形式為:
const store = createStore(reducer, applyMiddleware(...))
這個時候就會執行上面源碼中的代碼,使函數調用滿足最基本的形式調用。也就是函數在傳遞兩個或者三個參數的情況下,其內部處理邏輯都是一樣的。
// 如果我們指定了reducer增強器enhancer if (typeof enhancer !== "undefined") { // enhancer必須是一個函數 if (typeof enhancer !== "function") { throw new Error("Expected the enhancer to be a function.") } // 這個函數接收createStore作為參數,并且返回一個函數,這個函數接收的參數是reducer,preloadedState // 直接返回經過enhancer包裝的對象 return enhancer(createStore)(reducer, preloadedState) }
想更好的理解這段代碼,可以參考applyMiddleware內部的實現。
// 要求傳遞給createStore的第一個參數必須是一個函數 if (typeof reducer !== "function") { throw new Error("Expected the reducer to be a function.") }
// 保存初始的reducer let currentReducer = reducer // 保存初始的state let currentState = preloadedState // 保存所有的事件監聽器 let currentListeners = [] // 獲取當前監聽器的一個副本(相同的引用) let nextListeners = currentListeners // 是否正在派發action let isDispatching = false function ensureCanMutateNextListeners() { // 如果nextListeners和currentListeners具有相同的引用,則獲取一份當前事件監聽器集合的一個副本保存到nextListeners中 if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
上面就是createStore方法中的一些初始參數,這里有一個地方值得思考:為什么要維護兩份事件監聽器列表(nextListeners,currentListeners)?。下面,我們會解釋。
// 直接返回當前store的state function getState() { return currentState }
function subscribe(listener) { // 事件監聽器必須是函數,否則會拋出異常 if (typeof listener !== "function") { throw new Error("Expected listener to be a function.") } // 這個事件監聽器是否已經被取消的標志(個人感覺:這個初始值應該被設置為false,語意化更好一些。) let isSubscribed = true // 調用這個函數的結果就是生成一份當前事件監聽器的一個副本保存到nextListeners中 ensureCanMutateNextListeners() // 將新的事件監聽器添加到nextListeners中 nextListeners.push(listener) // 返回一個取消監聽的函數 return function unsubscribe() { // 如果這個監聽器已經被取消了,則直接return if (!isSubscribed) { return } // 將監聽器是否取消的標志設置為false isSubscribed = false // 再次生成一份事件監聽器集合的副本 ensureCanMutateNextListeners() // 獲取到需要取消的事件監聽器的索引 const index = nextListeners.indexOf(listener) // 從事件監聽器集合中刪除這個事件監聽器 nextListeners.splice(index, 1) } }
從subscribe方法的源碼中可以看出,每次在進行監聽器的添加/刪除之前,都會基于當前的監聽器集合生成一個副本保存到nextListeners中。這個時候還是不能準確的回答上面的問題,下面我們繼續研究dispatch的源碼:
function dispatch(action) { // dispatch的參數就是我們需要派發的action,一定要保證這個action是一個純粹的對象 // 如果不是一個純粹的對象,則會拋出異常。 if (!isPlainObject(action)) { // 這個方法有坑,在低版本的IE瀏覽器中性能很差,最后我們會研究這個方法的源碼。 throw new Error( "Actions must be plain objects. " + "Use custom middleware for async actions." ) } // 所派發的action必須有一個type屬性(我們可以將這個屬性認為就是action的身份證,這樣redux才知道你派發的是哪個action,你需要做什么,該怎么為你做) // 如果沒有這個屬性則會拋出異常 if (typeof action.type === "undefined") { throw new Error( "Actions may not have an undefined "type" property. " + "Have you misspelled a constant?" ) } // 如果redux正在派發action,則拋出異常?什么時候會出現這種情況??? if (isDispatching) { throw new Error("Reducers may not dispatch actions.") } try { isDispatching = true // 派發action // 實質就是將當前的state和你需要派發的action傳遞給reducer函數病返回一個新的state currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 這一塊也是一個十分關鍵的地方,哈哈哈哈哈,又多了一份事件監聽器的列表,簡單的說一下這三份列表的作用 // nextListeners: 保存這次dispatch后,需要觸發的所有事件監聽器的列表 // currentListeners: 保存一份nextListeners列表的副本 // listeners: 需要執行的列表 const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] // 調用所有的事件監聽器 listener() } // dispatch的返回值也是十分重要的,如果沒有這個返回值,就不可能引入強大的中間件機制。 return action }
到這里,我們就可以回答這個問題了:為什么要維護兩份事件監聽器列表(nextListeners,currentListeners)?
首先,我們必須要知道的事情就是:我們的監聽器在什么時候會執行?在我們的調用dispatch派發action之后。ok,看下面的這個圖:
這個圖表示,當dispatch方法執行到這行代碼的時候,listeners,currentListeners,nextListeners這三個變量引用內存中的同一份數組,只要其中一個發生變化,另外兩個立馬改變。和下面的這個例子一樣的含義:
所以,在這種情況下。如果我在某個事件監聽器函數中調用了取消了某個監聽器,那么在這次dispatch后,被取消的這個事件監聽器就不會被執行了(?????是嗎????)。
import {createStore, combineReducers, applyMiddleware} from "../src" import logger from "redux-logger" const actionTypes = "@@redux/INIT" const reducers = (state = {}, action) => { switch(action.type) { case actionTypes: return { "type": actionTypes } default: return state } } const store = createStore(reducers, applyMiddleware(logger)) const listener1 = store.subscribe(() => { console.log("listener1") }) const listener2 = store.subscribe(() => { // 取消listener3 listener3() console.log("listener2") }) const listener3 = store.subscribe(() => { console.log("listener3") }) store.dispatch({type: actionTypes})
結果是:
listener1 listener2 listener3
結果,就是:即使你在某個事件監聽器中,取消了其它的事件監聽器,那么被取消的這個事件監聽器,在這次dispatch后仍然會執行。也就是說。redux會保證在某個dispatch后,會保證在這個dispatch之前的所有事件監聽器全部執行。
這是個bug還是個feature。無從而知,但是從redux源碼中,可以知道,這是一個bug。所以,redux作者就利用上面的方法很巧妙的避免了這種情況。其實實現的方法很簡單:切斷nextListeners和currentListener,listeners相同的引用關系。
下面接著扯:
// 提換reducer的方法。(動態加載reducers的時候才用) function replaceReducer(nextReducer) { if (typeof nextReducer !== "function") { throw new Error("Expected the nextReducer to be a function.") } currentReducer = nextReducer // 替換結束后,重新初始化 dispatch({ type: ActionTypes.INIT }) }
// 觸發預設action,主要就是為了生成初始的state tree的結構 dispatch({ type: ActionTypes.INIT })
// 這就很熟悉了吧 return { dispatch, subscribe, getState, replaceReducer, // 尼瑪 忽略這個 [$$observable]: observable }
這就是對createStore源碼的一個整體解讀,水平有限,歡迎拍磚。后續的源碼解讀和測試例子可以關注:redux源碼解讀倉庫
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89454.html
摘要:的中間件主要是通過模塊實現的。返回的也是一個對象這個其實就是,各個中間件的最底層第三層的哪個函數組成的圓環函數構成的這就是對源碼的一個整體解讀,水平有限,歡迎拍磚。后續的源碼解讀和測試例子可以關注源碼解讀倉庫 applyMiddleware源碼解析 中間件機制在redux中是強大且便捷的,利用redux的中間件我們能夠實現日志記錄,異步調用等多種十分實用的功能。redux的中間件主要是...
摘要:否則的話,認為只是一個普通的,將通過也就是進一步分發。在本組件內的應用傳遞給子組件源碼解析期待一個作為傳入,里面是如果只是傳入一個,則通過返回被綁定到的函數遍歷并通過分發綁定至將其聲明為的屬性之一接收的作為傳入。 原文鏈接:https://github.com/ecmadao/Co...轉載請注明出處 本文不涉及redux的使用方法,因此可能更適合使用過redux的玩家翻閱? 預熱...
摘要:函數組合,科里化的串聯結合示例源碼,實現也很優雅,對于返回的,將等參數傳遞進去,然后執行,等待回調異步完成再。對于正常對象則進行下一步。前言 作為前端狀態管理器,這個比較跨時代的工具庫redux有很多實現和思想值得我們思考。在深入源碼之前,我們可以相關注下一些常見問題,這樣帶著問題去看實現,也能更加清晰的了解。 常見問題 大概看了下主要有這么幾個: redux三大原則 這個可以直接參考...
摘要:這里還有一個疑問點就是的嵌套,最開始也我不明白,看了源碼才知道,這里返回的也是接受也就是一個所以可以正常嵌套。以作為參數,調用上一步返回的函數以為參數進行調用。 1、本文不涉及redux的使用方法,因此可能更適合使用過 redux 的同學閱讀2、當前redux版本為4.0.13、更多系列文章請看 Redux作為大型React應用狀態管理最常用的工具。雖然在平時的工作中很多次的用到了它...
摘要:主模塊的入口模塊就是。主要就做兩件事引入個功能模塊,并掛載至同一個對象上,對外暴露。在非環境下壓縮代碼,給予警告。后續的源碼解讀和測試例子可以關注源碼解讀倉庫 主模塊 redux的入口模塊就是src/index.js。這個文件的代碼十分簡單。主要就做兩件事: 引入個功能模塊,并掛載至同一個對象上,對外暴露。 在非production環境下壓縮代碼,給予警告。 下面是模塊的源碼(只包...
閱讀 2022·2023-04-25 23:30
閱讀 1452·2021-11-24 10:18
閱讀 3070·2021-10-09 09:54
閱讀 2017·2021-10-08 10:05
閱讀 3431·2021-09-23 11:21
閱讀 3161·2019-08-30 15:52
閱讀 1560·2019-08-30 13:05
閱讀 1056·2019-08-30 13:02