redux原始碼解析(深度解析redux)
redux原始碼解析
1、首先讓我們看看都有哪些內容
2、讓我們看看redux的流程圖
Store:一個庫,儲存資料的地方,整個專案只有一個
建立store
Redux提供 creatStore 函式來生成 Store
// 引入redux import { createStore } from 'redux'; //建立Store 需要傳遞一個函式fn 這裡的fn是之後會提及的reducers const store = createStore(fn);
State:狀態,某時刻的資料即是Store的狀態
獲取狀態的方法是store.getState()
Action:行為,它有一個不可或缺的type屬性
action還可以攜帶其他內容
我們可以使用action來改變State的值,
從而將我們需要的資料通過Action“運輸”到 Store;
dispatch:傳送action
dispatch(action)接受一個action物件為引數,並將它傳送出去,
Store接受Action,接受之後需要返回一個新的State(狀態)
Reducer:處理器
dispatch(action)接受一個action物件為引數,並將它傳送出去,
Store接受Action,接受之後需要返回一個新的State(狀態)
而建立這個新的狀態的過程就是reducer
3、從isPlainObject.js開始
/** * @param {any} obj The object to inspect. * @returns {boolean} True if the argument appears to be a plain object. */ export default function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) return false let proto = obj while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto }
· 這個函式的核心思想在於什麼呢?
在於判斷一個值是否為一個普通的物件
此處的普通物件指的是直接通過字面量(let obj={})或者new Object()創建出來的物件
· 那麼他是怎麼做判斷的呢?
if (typeof obj !== 'object' || obj === null) return false
這行程式碼排除掉肯定不是物件的值
注意:typeof null 的返回值為 "object". 所以只使用 typeof obj !== 'object' 不能將 null 值排除掉. 因此應使用 typeof obj !== 'object' || obj === null 進行判斷.
再往下就是通過原型鏈判斷了.
通過 while 不斷地判斷 Object.getPrototypeOf(proto) !== null 並執行,
最終 proto 會指向 Object.prototype. 這時再判斷 Object.getPrototypeOf(obj) === proto,
如果為 true 的話就代表 obj 是通過字面量或呼叫 new Object() 所建立的物件了.
Object.getPrototypeOf()
方法用於獲取一個物件的原型屬性指向的是哪個物件.
舉個:chestnut:: 假設有一個構造器:function Fun(){} 建立一個物件:var f = new Fun() Object.getPrototypeOf(f) 得到的返回值 和訪問 f.__proto__ 是一樣的 這個值指向 Fun.prototype.
假如一個物件是普通物件
那麼這個物件的 __proto__ 一定是指向 Object.prototype 的,
而非普通物件, 例如 f, 其 __proto__ 是指向其建構函式的 prototype 屬性.
因此比較 Object.getPrototypeOf(obj) 與 proto 相等, 則判定 obj 是普通物件.
4、接下來是createStore.js
//如果第二個引數為方法且第三個引數為空,則將兩個引數交換 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } //reducer需要是個方法 if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } //不支援這樣寫 if ( (typeof preloadedState === 'function' && typeof enhancer === 'function') || (typeof enhancer === 'function' && typeof arguments[3] === 'function') ) { throw new Error( 'It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function' ) } let currentReducer = reducer 當前的reducer函式 let currentState = preloadedState 當前的state樹 let currentListeners = [] 監聽函式列表 let nextListeners = currentListeners 監聽列表的一個引用 let isDispatching = false 是否正在dispatch
裡面的幾個函式
getState()
//返回當前state樹 function getState() { if (isDispatching) { throw new Error( 'You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.' ) } return currentState }
subscribe()
//這個函式用於給store新增監聽函式,把需要新增的監聽函式作為引數傳入即可 //nextListeners 即為目前的監聽函式列表,添加了之後,subscribe方法會返回一個unsubscribe()方法 //此方法用於登出剛才新增的監聽函式。 function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } if (isDispatching) { throw new Error( 'You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } if (isDispatching) { throw new Error( 'You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
dispatch()
function dispatch(action) { //action必須是一個包含type的物件 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?' ) } //如果正處於isDispatching狀態,報錯 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true //這裡就是呼叫我們reducer方法的地方,返回一個新的state作為currentState currentState = currentReducer(currentState, action) } finally { isDispatching = false } //呼叫所有的監聽函式 const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action } 它只是執行了當前的reducer方法, 然後把當前的state和你在呼叫dispatch時傳入的action作為引數, 返回的值就是新的currentState。 從這裡我們也可以看出,改變state的程式碼邏輯就在reducer方法中, 在這些執行完之後,dispatch方法會遍歷當前的監聽列表,並執行所有的監聽函式。
replaceReducer()
function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) } 替換reducer之後重新初始化狀態樹
observable()
//是一種觀察者模式的思想 function observable() { const outerSubscribe = subscribe return { /** * The minimal observable subscription method. * @param {Object} observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns {subscription} An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. */ subscribe(observer) { if (typeof observer !== 'object' || observer === null) { throw new TypeError('Expected the observer to be an object.') } //觀察者模式的鏈式結構,傳入當前的state function observeState() { if (observer.next) { observer.next(getState()) } } observeState() const unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, [$$observable]() { return this } } }
5、接下來就是compose.js
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))) }
reduce方法接受2個引數,第一個引數是一個callback函式,第二個是一個初始值initValue
第一個函式有四個引數
- previousValue: 上一次呼叫callback時返回的值
- currentValue: 正在處理的陣列元素
- index: 正在處理的陣列元素下標
- array: 被處理的陣列
如果有initValue,initValue將作為第一次的previousValue,若沒有,則陣列第一個元素將作為previousValue,
後面一個元素將作為currentValue,然後執行callback的函式體,將返回的值作為previousValue,
將下一個元素作為currentValue,一直到最後一個數組最後一個元素執行完位置,再返回最終的結果。
比如有一個數組arr=[1,2,3,4,5],我們使用reduce來求和:
let sum = [1,2,3,4,5].reduce((a,b)=>a+b);
它巧妙的地方在於陣列的每個元素都是函式,
callback返回一個複合函式作為previousValue,在reduce方法執行完之後,
也就返回了一個將整個陣列中所有函式串式呼叫的一個函式。
6、然後是applyMiddleware.js
export default function applyMiddleware(...middlewares) { //return一個函式,可以接收createStore方法作為引數 //給返回的store的dispatch方法再進行一次包裝 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) } //傳入middlewareAPI引數並執行每一個外部函式,返回結果匯聚成陣列 const chain = middlewares.map(middleware => middleware(middlewareAPI)) //用到了上面的compose方法 dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
還記得剛才createStore方法中的enhancer引數嗎?
applyMiddleware就是用來建立enhancer函式的。
就是使用applyMiddleware的一個很好的例子,
我們結合它的程式碼來看可以更好的理解,下面是它的程式碼:
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;
最終export了一個接受{ dispatch, getState }作為引數的function thunk,
這個thunk方法也就是傳給applyMiddleware方法的引數,
此時的middlewares只有thunk一個方法,
那麼applyMiddleware中的chain也就很顯然的是執行了thunk方法後返回的結果,
我們再看redux-thunk的程式碼,返回了一個接受next作為引數的方法A!
applyMiddleware的下一行,
dispatch = compose(...chain)(store.dispatch),
chain只有一個function,所以這裡可以忽略compose,
那麼這一句就是將store.dispatch 作為next引數傳給了剛才的方法A,
終於,方法A返回了我們熟悉的dispatch方法。
但是注意,此時的dispatch方法還是原來的dispatch方法嗎?
它已經不是原來的它了。經過thunk方法的包裝,早已物是人非。
我們來看一下redux-thunk的程式碼,第三行之後的4行,
如果dispatch方法接受的引數不是一個function,
那麼這個dispatch就和普通的dispatch沒什麼不同,
但如果此時的action是一個方法,那麼就會執行此方法,且第一個引數是store.dispatch。
這意味著我們的action建立函式不再只能建立一個包含type的Object,而可以是一個方法。
你可能會問有什麼用呢?當你在action中需要一個非同步操作,並需要在回撥中改變state的狀態的時候,這就是一個絕佳的解決方案。
所以說,applyMiddleware實際上做了一件事,就是根據外部函式(中介軟體函式)包裝原來的dispatch函式,然後將新的dispatch函式暴露出去。
再回頭去看createStore.jsx中的 return enhancer(createStore)(reducer, preloadedState)這句程式碼,是不是明白了很多事情?
7、combineReducers.js
//很簡單卻很關鍵,我就不解釋了~ function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) } /** * 將action與dispatch函式繫結,生成直接可以觸發action的函式, * 可以將第一個引數物件中所有的action都直接生成可以直接觸發dispatch的函式 * 而不需要一個一個的dispatch,生成後的方法對應原來action生成器的函式名 * */ export default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } //actionCreators必須為object型別 if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error( `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } const keys = Object.keys(actionCreators) const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] //給actionCreators的每一個成員都繫結dispatch方法生成新的方法, //然後注入新的物件中,新方法對應的key即為原來在actionCreators的名字 if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } else { warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`) } } return boundActionCreators 這個方法主要的作用就是將action與dispatch函式繫結,生成直接可以觸發action的函式。程式碼比較簡單註釋也比較明白,就過去了~
8、bindActionCreators.js
//根據key和action生成錯誤資訊 function getUndefinedStateErrorMessage(key, action) { //... } //一些警告級別的錯誤 function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) { const reducerKeys = Object.keys(reducers) const argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer' //判斷reducers是否為空陣列 //判斷state是否是物件 //給state中存在而reducer中不存在的屬性新增快取標識並警告 //... } //這個方法用於檢測用於組合的reducer是否是符合redux規定的reducer function assertReducerSanity(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] //呼叫reducer方法,undefined為第一個引數 //使用前面說到過的ActionTypes.INIT和一個隨機type生成action作為第二個引數 //若返回的初始state為undefined,則這是一個不符合規定的reducer方法,丟擲異常 //... }) } export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) //所有的鍵名 const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (process.env.NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } } //finalReducers是過濾後的reducers,它的每一個屬性都是一個function if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} } let sanityError //檢測每個reducer是否是符合標準的reducer try { assertReducerSanity(finalReducers) } catch (e) { sanityError = e } return function combination(state = {}, action) { if (sanityError) { throw sanityError } //如果不是成產環境,做一些警告判斷 if (process.env.NODE_ENV !== 'production') { const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } let hasChanged = false const nextState = {} //下一個state樹 //遍歷所有reducers,然後將每個reducer返回的state組合起來生成一個大的狀態樹,所以任何action,redux都會遍歷所有的reducer for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) //如果此reducer返回的新的state是undefined,丟擲異常 if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } //如果當前action對應的reducer方法執行完後,該處資料沒有變化,則返回原來的流程樹 return hasChanged ? nextState : state } }
未完待續。