摘要:一個沒有返回值都會有警告,所以我們寫的時候都會指定一個默認返回值。執行接收參數,如果參數個數是,直接執行,上文的的執行結果返回值是一個函數,作為參數的話,長度確實是,所以直接返回了,也就是,所以這塊是不需要的。
redux源碼解析 什么是redux
Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。
為什么需要使用redux提供了和雙向綁定思想不同的單向數據流,應用狀態可以預測,可以回溯,易于調試。使用redux之初的人可能會很不適應,改變一個狀態,至少寫三個方法,從這點上不如寫其他框架代碼易于理解,但是自從配合使用redux-logger一類的logger插件,就感覺到了redux的優勢。狀態改變很清晰,很容易了解發生了什么。
源碼解析注意: 如果沒有使用過redux,建議先去看看redux文檔
api方法export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose }
可以看到我們在react代碼中使用到的api,一般主動調用的就是 combineReducers ,其他部分參照例子基本可以搬過來
combineReducers打開combineReducers.js,先看export的方法,也就是combineReducers方法
var reducerKeys = Object.keys(reducers) var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { var key = reducerKeys[i] if (process.env.NODE_ENV !== "production") { if (typeof reducers[key] === "undefined") { warning(`No reducer provided for key "${key}"`) } } if (typeof reducers[key] === "function") { finalReducers[key] = reducers[key] } }
首先看到這個函數接收的是一個對象,而這個這個對象的內部數據值必須是一個函數,不然會警告。循環了一遍這個對象,得到一個新值,對象值全部是函數的一個新reducers
var finalReducerKeys = Object.keys(finalReducers) if (process.env.NODE_ENV !== "production") { var unexpectedKeyCache = {} } var sanityError try { assertReducerSanity(finalReducers) } catch (e) { sanityError = e }
這里好像還在判斷這個最后reducers的合法性,那這里是在判斷什么呢?我們來看看 assertReducerSanity 方法
function assertReducerSanity(reducers) { Object.keys(reducers).forEach(key => { var reducer = reducers[key] var initialState = reducer(undefined, { type: ActionTypes.INIT }) if (typeof initialState === "undefined") { throw new Error( `Reducer "${key}" returned undefined during initialization. ` + `If the state passed to the reducer is undefined, you must ` + `explicitly return the initial state. The initial state may ` + `not be undefined.` ) } var type = "@@redux/PROBE_UNKNOWN_ACTION_" + Math.random().toString(36).substring(7).split("").join(".") if (typeof reducer(undefined, { type }) === "undefined") { throw new Error( `Reducer "${key}" returned undefined when probed with a random type. ` + `Don"t try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` + `namespace. They are considered private. Instead, you must return the ` + `current state for any unknown actions, unless it is undefined, ` + `in which case you must return the initial state, regardless of the ` + `action type. The initial state may not be undefined.` ) } }) }
這塊其實就兩個判斷,reducer被執行了兩次,一個是判斷沒有初始化state的,reducer的返回值,一個判斷action沒有type的時候的返回值。一個沒有返回值都會有警告,所以我們寫reducer的時候都會指定一個默認返回值。
reducer會被執行多次,這也是我們為什么要保證reducer的純粹性,不能做任何其他的操作的原因
繼續往下看 combineReducers
可以看到返回了一個函數 combination(state = {}, action) 。為什么返回函數呢?
那我們看 combination(state = {}, action) 像什么?不就是我們經常寫的reducer嘛!這個reducer最終會被store傳入初始state并且當作純函數調用,而reducer里面是可以嵌套combineReducers的結果的,所以我們在使用狀態的時候,經常會這樣 state.user.login 這樣子的類似狀態調用
這塊想明白還是有點復雜,所有的reducer都是一個相同的函數combination,接收state參數,內部執行同樣是combination,直到沒有combineReducers為止,才開始執行我們自己寫的reducer函數,得到的值使用combineReducers參數的對象的key作為state的key,我們自己寫的reducers執行結果得到的值作為state的value。最終得到的就是一個巨大的Object,這就是我們的store中的state。
createStore一般這個方法我們可以直接從demo中復制過來,不需要太過了解,但是既然要深入了解redux,必然要掌握這個方法
跟之前一樣,先找到 export createStore 方法,可以看到這個函數接受三個參數
export default function createStore(reducer, preloadedState, enhancer) {
第一個reducer: 上文講到的combineReducer返回的reducer函數
第二個preloadedState:redux初始化state,可以不傳
第三個enhancer:中間件
if (typeof preloadedState === "function" && typeof enhancer === "undefined") { enhancer = preloadedState preloadedState = undefined } 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置為undefined
第二個判斷的意思是當有中間件參數,但是中間參數類型不是function的時候,拋出一個非法錯誤,如果是函數,先執行中間件,退出。后續在講中間件是怎么執行的
第三個判斷reducer是否是函數,否則拋出錯誤退出
var currentReducer = reducer // 當前reducer var currentState = preloadedState // 當前state var currentListeners = [] // 當前監聽器 var nextListeners = currentListeners // 下一個監聽器 var isDispatching = false // 重復dispatch的狀態標記
再看看createStore的返回值
return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }
這不是store的方法嘛,挨個看看
function getState() { return currentState }
這個沒什么好說的。
function subscribe(listener) { if (typeof listener !== "function") { throw new Error("Expected listener to be a function.") } var isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() var index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
發布訂閱模式,熟悉事件系統的應該比較明白,注冊一個方法而已,結果返回一個取消監聽方法
function dispatch(action) { if (!isPlainObject(action)) { throw new Error( "Actions must be plain objects. " + "Use custom middleware for async actions." ) } if (typeof action.type === "undefined") { throw new Error( "Actions may not have an undefined "type" property. " + "Have you misspelled a constant?" ) } if (isDispatching) { throw new Error("Reducers may not dispatch actions.") } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } var listeners = currentListeners = nextListeners for (var i = 0; i < listeners.length; i++) { listeners[i]() } return action }
老幾樣啊,先做一些判斷,我們寫代碼的時候好像沒這么嚴謹哈。執行reducer,觸發所有listeners。這個比較簡單。
這樣子,看起來createStore沒什么復雜的,復雜的在哪呢?我們掠過的中間件退出的環節。所以來燒腦吧,看看中間件
想想我們創建store的時候是怎么操作的
const finalCreateStore = compose( applyMiddleware(thunk, logger) )(createStore) const store = finalCreateStore(rootReducer, initialState)
這種堆在一起的代碼不是太好看,分開,分開
const middlewares = applyMiddleware(thunk, logger) const composeResult = compose(middlewares) const finalCreateStore = composeResult(createStore) const store = finalCreateStore(rootReducer, initialState)
這就條理清晰多了,看代碼一定要看懂流程,按照順序看,不然一頭霧水,先看第一步 applyMiddleware
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
可以看到這個方法返回一個函數,既然這個函數沒有被執行到,我們就先不看,現在我們得到了一個 applyMiddleware 返回的函數了
接著看 compose 方法了
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
代碼更少,可是redux精髓全在這了。
compose 執行接收參數,如果參數個數是1,直接執行,上文的 applyMiddleware 的執行結果返回值是一個函數middlewares,作為參數的話,長度確實是1,所以直接返回了middlewares,也就是composeResult,所以這塊是不需要compose的。而這個參數函數接收一個參數就是createStore,剛好接收createStore方法,所以我們還是進入到 applyMiddleware 的返回函數里面看看
顯然 composeResult 接收到 createStore之后返回一個函數: finalCreateStore,從代碼中可以看出也是可以接收中間件方法的,不過應該不會有人再在這里重復添加中間件了。
進入到 finalCreateStore 中看看
創建了store,前文已經講過了
把所有的middlewares執行一遍,從這里可以看出middlewares是一個接收 { dispatch, getState } 參數的函數,不可能有其他情況
把middlewares執行的結果數組作為參數再一次傳入了compose
再次進入到 compose 中看邏輯,如果只有一個中間件的話,同樣是把中間件直接返回,如果超過一個執行下面的邏輯
const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
compose 同樣只是返回了一個函數。這個函數接收的參數在 applyMiddleware 里面能看到接收到的是dispatch方法
這里巧妙的利用了js Array的reduce方法,reduce方法的原理就是回調函數的返回值作為后一個回調函數的第一個參數,第一個回調函數的第一個參數的值是 reduce方法的第二個參數值。
args就是dispatch方法,這里看的出中間件函數還得返回函數,這個函數得接收類似dispatch方法的函數
看看redux-chunk這個中間件的實現吧
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === "function") { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
看到 next 方法,使用過express的同學應該會很熟悉,這個next和express的next很像,原理也類似。
每個中間件的最后一層函數都是一個next,才可以在reduce里面作為參數傳遞,才可以實現中間件的傳遞
這也是redux名稱的由來。
redux代碼短小精悍,設計精巧,真好。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82630.html
摘要:總體概括是官方推薦的一個狀態管理庫。功能強大且代碼優雅。在閱讀源碼的過程中可以看出,其只依賴這兩個庫的某幾個方法。從這里來看,可以看作是無依賴的一個庫。這就是對源碼的整體概括,水平有限,歡迎拍磚。后續的源碼解讀和測試例子可以關注源碼解讀倉庫 Redux總體概括 redux是react官方推薦的一個狀態管理庫。功能強大且代碼優雅。從package.json文件中: dependencie...
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應一個文件。總結上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關生態的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態管理,以及Redux-saga中...
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應一個文件。總結上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關生態的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態管理,以及Redux-saga中...
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應一個文件。總結上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關生態的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態管理,以及Redux-saga中...
摘要:到月底了,小明的爸爸的單位發了工資總計塊大洋,拿到工資之后第一件的事情就是上交,毫無疑問的,除非小明爸爸不要命了。當小明的爸爸收到這個通知之后,心的一塊大石頭也就放下來了。下面我們正式開始我們的源碼閱讀之旅。 前言 用過react的小伙伴對redux其實并不陌生,基本大多數的React應用用到它。一般大家用redux的時候基本都不會單獨去使用它,而是配合react-redux一起去使用...
摘要:主模塊的入口模塊就是。主要就做兩件事引入個功能模塊,并掛載至同一個對象上,對外暴露。在非環境下壓縮代碼,給予警告。后續的源碼解讀和測試例子可以關注源碼解讀倉庫 主模塊 redux的入口模塊就是src/index.js。這個文件的代碼十分簡單。主要就做兩件事: 引入個功能模塊,并掛載至同一個對象上,對外暴露。 在非production環境下壓縮代碼,給予警告。 下面是模塊的源碼(只包...
閱讀 2030·2023-04-26 02:15
閱讀 2306·2021-11-19 09:40
閱讀 1044·2021-10-27 14:13
閱讀 3313·2021-08-23 09:44
閱讀 3614·2019-12-27 12:24
閱讀 657·2019-08-30 15:53
閱讀 1169·2019-08-30 10:53
閱讀 2163·2019-08-26 12:14