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