第一次看原始碼,並沒有想象中的難哈,主要是redux的原始碼比較少,理解起來也比較簡單。看過之後,感覺更深入的理解了redux思想和函數語言程式設計的理念,建議大家可以去看一下嘻嘻,看完之後肯定會有收穫的。
我是對照著網上別人看過的原始碼筆記看的,寫這篇文章的原因呢,是想總結一下,因為我記性比較差啦,算是做一個筆記,方便以後複習。
redux的原始碼中,有6個js檔案,分別是:
- index.js
- createStore.js
- combineReducers.js
- bindActionCreators.js
- compose.js
- applyMiddleware.js
我們一個一個來分析吧~
index
這裡呢沒有太多需要講的,就是暴露了5個核心的api,分別是:
- createStore:接收state和reducer,生成一顆狀態樹store
- combineReducers:把子reducer合併成一個大reducer
- bindActionCreators:把actionCreators和dispatch封裝成一個函式,也就是把他們兩繫結在一起
- applyMiddleware:這是一箇中介軟體
- compose:一個組合函式
createStore
首先,定義初始化的action
export const ActionTypes = {
INIT: '@@redux/INIT'
}
複製程式碼
這個createStore函式,會傳入三個引數和一個返回值,分別是:
1、 @param {Function} reducer
這個reducer是一個函式,這個函式接收state和action,作一系列計算之後返回一個新的state。這裡就體現了函數語言程式設計的一些特性:
第一,這個reducer是一個純函式,純函式的特點是:對於相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用,也不依賴外部環境的狀態。不理解純函式的筒子們,可以上網搜尋一下。
第二,state是不可變的,我們這裡對state作的一些修改和計算,不是直接修改原來的資料,而是返回修改之後的資料,原來的資料是保持不變。這裡可以衍生到immutable,可以使用immutable和redux搭配使用。
2、@param {any} [preloadedState]
這是初始化的state,不多說。
3、@param {Function} [enhancer]
這個enhancer其實就是一箇中介軟體,它在redux3.1.0之後才加入的。相當於把store做一些增強處理,讓store更強大,功能更豐富,在之後的applyMiddleware那裡會詳細說的。這裡也體現了高階函式的思想,就像react-redux的connect方法一樣,做一些包裝處理之後,再返回。
4、@returns {Store}
這是返回的值,返回的是一棵狀態樹,也就是store啦。
這是做的原始碼分析,都寫在註釋裡了。createStore返回的最常用的三個api是dispatch,subscribe,getState,一般我們只要傳入reducer和preloadedState,就可以直接呼叫這三個方法,非常方便。
export default function createStore(reducer, preloadedState, enhancer) {
//這裡是一些引數校驗
//如果第二個引數為函式且沒有傳入第三個引數,那就交換第二個引數和第三個引數
//意思是createSotre會認為你忽略了preloadedState,而傳入了一個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.')
}
//這是一個高階函式呼叫方法。這裡的enhancer就是applyMiddleware(...middlewares)
//enhancer接受createStore作為引數,對createStore的能力進行增強,並返回增強後的createStore
//然後再將reducer和preloadedState作為引數傳給增強後的createStore,得到最終生成的store
return enhancer(createStore)(reducer, preloadedState)
}
//reducer不是函式,報錯
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
//宣告一些變數
let currentReducer = reducer //當前的reducer函式
let currentState = preloadedState//當前的狀態樹
let currentListeners = [] // 當前的監聽器列表
let nextListeners = currentListeners //更新後的監聽器列表
let isDispatching = false //是否正在dispatch
//判斷當前listener和更新後的listener是不是同一個引用,如果是的話對當前listener進行一個拷貝,防止在操作新的listener列表的時候對正在發生的業務邏輯造成影響
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
*
* @returns {any} The current state tree of your application.
*/
//返回當前的狀態樹
function getState() {
return currentState
}
/**
*這個函式是給store新增監聽函式,把listener作為一個引數傳入,
*註冊監聽這個函式之後,subscribe方法會返回一個unsubscribe()方法,來登出剛才新增的監聽函式
* @param {Function} listener 傳入一個監聽器函式
* @returns {Function}
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
//註冊監聽
let isSubscribed = true
ensureCanMutateNextListeners()
//將監聽器壓進nextListeners佇列中
nextListeners.push(listener)
//註冊監聽之後,要返回一個取消監聽的函式
return function unsubscribe() {
//如果已經取消監聽了,就返回
if (!isSubscribed) {
return
}
//取消監聽
isSubscribed = false
//在nextListeners中找到這個監聽器,並且刪除
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
/**
* @param {Object} action 傳入一個action物件
*
* @returns {Object}
*/
function dispatch(action) {
//校驗action是否為一個原生js物件
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
//校驗action是否包含type物件
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.')
}
//設定正在派發的標誌位,然後將當前的state和action傳給當前的reducer,用於生成新的state
//這就是reducer的工作過程,純函式接受state和action,再返回一個新的state
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
//得到新的state之後,遍歷當前的監聽列表,依次呼叫所有的監聽函式,通知狀態的變更
//這裡沒有把最新的狀態作為引數傳給監聽函式,是因為可以直接呼叫store.getState()方法拿到最新的狀態
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
//返回action
return action
}
/**
*這個方法主要用於reducer的熱替換
* @param {Function} nextReducer
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// 把傳入的nextReducer給當前的reducer
currentReducer = nextReducer
//dispatch一個初始action
dispatch({ type: ActionTypes.INIT })
}
/**
* 用於提供觀察者模式的操作,貌似是一個預留的方法,暫時沒看到有啥用
* @returns {observable} A minimal observable of state changes.
*/
function observable() {
const outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param {Object} observer
* 觀察者應該有next方法
* @returns {subscription}
*/
subscribe(observer) {
//觀察者模式的鏈式結構,傳入當前的state
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.')
}
//獲取觀察者的狀態
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
//返回一個取消訂閱的方法
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
//初始化一個action
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
複製程式碼
combineReducers
combineReducers的作用是將之前切分的多個子reducer合併成一個大的reducer,也就是說將很多的小狀態樹合併到一棵樹上,整合成一棵完整的狀態樹。
這個函式接受一個引數,返回一個函式
1、@param {Object} reducers
這裡接收多個reducer,傳入的是一個物件
2、@returns {Function}
combineReducers的整個執行過程就是:將所有符合標準的reducer放進一個物件中,當dispatch一個action的時候,就遍歷每個reducer,來計算出每個reducer的state值。同時,每遍歷一個reducer,就判斷新舊state是否發生改變,來決定是返回新state還是舊state,這是做的一個優化處理。
原始碼分析如下,前面還有一部分是一些error資訊和warning資訊的處理,就沒有放進來了,感興趣的話可以自己去看一下完整的原始碼。
export default function combineReducers(reducers) {
//獲取reducers的所有key值
const reducerKeys = Object.keys(reducers)
//最終生成的reducer物件
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}"`)
}
}
//遍歷reducer,把key值都是function的reducer放進finalReducers物件中
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
//得到finalReducers的key值陣列
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
//檢測這些reducer是否符合標準
let shapeAssertionError
try {
//檢測是否是redux規定的reducer形式
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
//計算state的邏輯部分
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
//如果不是production(線上)環境,做一些警告
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
//標誌state是否改變
let hasChanged = false
//儲存新的state
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
//遍歷finalReducerKeys的key值,也就是reducer的名字
const key = finalReducerKeys[i]
//得到reducer的vlaue值
const reducer = finalReducers[key]
//變化前的state值
const previousStateForKey = state[key]
//變化後的state值,把變化前的state和action傳進去,計算出新的state
const nextStateForKey = reducer(previousStateForKey, action)
//如果沒有返回新的reducer,就丟擲異常
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
//把變化後的state存入nextState陣列中
nextState[key] = nextStateForKey
//判斷state是否有改變
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
//如果改變了state就返回新的state,沒改變就返回原來的state
return hasChanged ? nextState : state
}
}
複製程式碼
bindActionCreators
bindActionCreators的作用是:將action與dispatch函式繫結,生成可以直接觸發action的函式。
//使用dispatch包裝actionCreator方法
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
/*
* @param {Function|Object} actionCreators
*
* @param {Function} dispatch
*
* @returns {Function|Object}
*
*/
export default function bindActionCreators(actionCreators, dispatch) {
//actionCreators為函式,就直接呼叫bindActionCreator進行包裝
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
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"?`
)
}
//以下是actionCreators為物件時的操作
//遍歷actionCreators物件的key值
const keys = Object.keys(actionCreators)
//儲存dispatch和actionCreator繫結之後的集合
const boundActionCreators = {}
//遍歷每一個物件,一一進行繫結
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
複製程式碼
compose
compose叫做函式組合,是一個柯里化函式,將多個函式合併成一個函式,從右到左執行。這同時也是函數語言程式設計的特性。這個函式會在applyMiddleware中用到
/**
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
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這個方法。這個reduce不是之前的reducer,這裡的reduce函式是es5的一個歸併陣列的方法,是從陣列的第一項開始,逐個遍歷陣列的所有項。
它接收兩個引數,一個是在每一項上呼叫的函式,還有一個可選引數,是作為歸併基礎的初始值。呼叫的那個函式又接收四個引數,前一個值,當前值,項的索引,和陣列物件。這個函式返回的任何值都會作為第一個引數自動傳遞給下一項。這樣說可能比較抽象,舉個例子:
[1,2,3,4,5].reduce((prev, cur) => {
return prev + cur //輸出15
})
複製程式碼
用reduce就可以很快的求的陣列所有值相加的和。另外,還有一個reduceRight()方法,跟reduce是一樣的,只不過是從陣列的右邊開始遍歷的。
我們回到原始碼上面return funcs.reduce((a, b) => (...args) => a(b(...args)))
,這其實就是遍歷傳入的引數陣列(函式),將這些函式合併成一個函式,從右到左的執行。這就是中介軟體的創造過程,把store用一個函式包裝之後,又用另一個函式包裝,就形成了這種包菜式的函式。
applyMiddleware
applyMiddleware是用來擴充套件redux功能的,主要就是擴充套件store.dispatch的功能,像logger、redux-thunk就是一些中介軟體。
它的實現過程是:在dispatch的時候,會按照在applyMiddleware時傳入的中介軟體順序,依次執行。最後返回一個經過許多中介軟體包裝之後的store.dispatch方法。
如果理解了之前說的compose函式,這一段程式碼應該也很容易就能看懂啦。
/**
* @param {...Function} middlewares 接收不定數量的中介軟體函式
* @returns {Function} 返回一個經過中介軟體包裝之後的store
*/
export default function applyMiddleware(...middlewares) {
//返回一個引數為createStore的匿名函式
return (createStore) => (reducer, preloadedState, enhancer) => {
//生成store
const store = createStore(reducer, preloadedState, enhancer)
//得到dispatch方法
let dispatch = store.dispatch
//定義中介軟體的chain
let chain = []
//在中介軟體中要用到的兩個方法
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
//把這兩個api給中介軟體包裝一次
chain = middlewares.map(middleware => middleware(middlewareAPI))
//鏈式呼叫每一箇中介軟體,給dispatch進行封裝,再返回最後包裝之後的dispatch
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
複製程式碼
總結
整個的原始碼就全部分析完了,我們可以看到,redux的原始碼很多地方都體現了函數語言程式設計的思想。函數語言程式設計寫出來的程式碼確實很漂亮很簡潔,但是理解起來也比較困難。這也只是函數語言程式設計的很小一部分,有興趣的話可以去了解一下其他的部分。
寫到這裡也差不多了,希望以後有機會能多看點原始碼,get一些新的知識,最後感謝宋老師的寶貴意見,bye