Redux作為大型React應(yīng)用狀態(tài)管理最常用的工具搬设。它是一個(gè)應(yīng)用數(shù)據(jù)流框架,與Flux框架類似膀捷。它是零依賴的迈嘹,可以配合其他框架或者類庫一起使用。雖然在平時(shí)的工作中很多次的用到了它全庸,但是一直沒有對其原理進(jìn)行研究秀仲。最近看了一下源碼,下面是我自己的一些簡單認(rèn)識壶笼。
- createStore
結(jié)合使用場景我們首先來看一下createStore方法神僵。
// 這是我們平常使用時(shí)創(chuàng)建store
const store = createStore(reducers, state, enhance);
以下源碼為去除異常校驗(yàn)后的源碼,
export default function createStore(reducer, preloadedState, enhancer) {
// 如果有傳入合法的enhance覆劈,則通過enhancer再調(diào)用一次createStore
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState) // 這里涉及到中間件保礼,后面介紹applyMiddleware時(shí)在具體介紹
}
let currentReducer = reducer //把 reducer 賦值給 currentReducer
let currentState = preloadedState //把 preloadedState 賦值給 currentState
let currentListeners = [] //初始化監(jiān)聽函數(shù)列表
let nextListeners = currentListeners //監(jiān)聽列表的一個(gè)引用
let isDispatching = false //是否正在dispatch
function ensureCanMutateNextListeners() {}
function getState() {}
function subscribe(listener) {}
function dispatch(action) {}
function replaceReducer(nextReducer) {}
// 在 creatorStore 內(nèi)部沒有看到此方法的調(diào)用,就不講了
function observable() {}
//初始化 store 里的 state tree
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
我們可以看到creatorStore方法除了返回我們常用的方法外责语,還做了一次初始化過程dispatch({ type: ActionTypes.INIT });那么dispatch干了什么事情呢炮障?
/**
* dispath action。這是觸發(fā) state 變化的惟一途徑坤候。
* @param {Object} 一個(gè)普通(plain)的對象胁赢,對象當(dāng)中必須有 type 屬性
* @returns {Object} 返回 dispatch 的 action
*/
function dispatch(action) {
// 判斷 dispahch 正在運(yùn)行,Reducer在處理的時(shí)候又要執(zhí)行 dispatch
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
//標(biāo)記 dispatch 正在運(yùn)行
isDispatching = true
//執(zhí)行當(dāng)前 Reducer 函數(shù)返回新的 state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
//遍歷所有的監(jiān)聽函數(shù)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener() // 執(zhí)行每一個(gè)監(jiān)聽函數(shù)
}
return action
}
這里dispatch主要做了二件事情
- 通過reducer更新state
- 執(zhí)行所有的監(jiān)聽函數(shù)白筹,通知狀態(tài)的變更
那么reducer是怎么改變state的呢智末?這就涉及到下面的combineReducers了。
- combineReducers
回到上面創(chuàng)建store的參數(shù)reducers遍蟋,
// 兩個(gè)reducer
const todos = (state = INIT.todos, action) => {
// ....
};
const filterStatus = (state = INIT.filterStatus, action) => {
// ...
};
const reducers = combineReducers({
todos,
filterStatus
});
// 這是我們平常使用時(shí)創(chuàng)建store
const store = createStore(reducers, state, enhance);
下面我們來看combineReducers做了什么
// 第一次篩選吹害,參數(shù)reducers為Object
// 篩選掉reducers中不是function的鍵值對
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// 二次篩選,判斷reducer中傳入的值是否合法(!== undefined)
// 獲取篩選完之后的所有key
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
// 遍歷所有的key和reducer虚青,分別將reducer對應(yīng)的key所代表的state它呀,代入到reducer中進(jìn)行函數(shù)調(diào)用
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// 這里就是reducer function的名稱和要和state同名的原因,傳說中的黑魔法
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 將reducer返回的值填入nextState
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 發(fā)生改變了返回新的nextState,否則返回原先的state
return hasChanged ? nextState : state
}
}
- 這里 reducer(previousStateForKey, action)執(zhí)行的就是我們上面定義的todos和filterStatus方法纵穿。通過這二個(gè)reducer改變state值下隧。
- 以前我一直很奇怪我們的reducer里的state是怎么做到取當(dāng)前reducer對應(yīng)的數(shù)據(jù)∥矫剑看到const previousStateForKey = state[key]這里我就明白了淆院。
- 這里還有一個(gè)疑問點(diǎn)就是combineReducers的嵌套,最開始也我不明白句惯,看了源碼才知道combineReducers()=> combination(state = {}, action)土辩,這里combineReducers返回的combination也是接受(state = {}, action)也就是一個(gè)reducer所以可以正常嵌套。
看到這初始化流程已經(jīng)走完了抢野。這個(gè)過程我們認(rèn)識了dispatch和combineReducers;接下來我們來看一下我們自己要怎么更新數(shù)據(jù)拷淘。
用戶更新數(shù)據(jù)時(shí),是通過createStore后暴露出來的dispatch方法來觸發(fā)的指孤。dispatch 方法启涯,是 store 對象提供的更改 currentState 這個(gè)閉包變量的唯一建議途徑(注意這里是唯一建議途徑,不是唯一途徑恃轩,因?yàn)橥ㄟ^getState獲取到的是state的引用结洼,所以是可以直接修改的。但是這樣就不能更新視圖了)叉跛。
正常情況下我們只需要像下面這樣
//action creator
var addTodo = function(text){
return {
type: 'add_todo',
text: text
};
};
function TodoReducer(state = [], action){
switch (action.type) {
case 'add_todo':
return state.concat(action.text);
default:
return state;
}
};
// 通過 store.dispatch(action) 來達(dá)到修改 state 的目的
// 注意: 在redux里,唯一能夠修改state的方法,就是通過 store.dispatch(action)
store.dispatch({type: 'add_todo', text: '讀書'});// 或者下面這樣
// store.dispatch(addTodo('讀書'));
也就是說dispatch接受一個(gè)包含type的對象松忍。框架為我們提供了一個(gè)創(chuàng)建Action的方法bindActionCreators昧互。
- bindActionCreators
下面來看下源碼
// 核心代碼挽铁,并通過apply將this綁定起來
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
// 如果actionCreators是一個(gè)函數(shù)伟桅,則說明只有一個(gè)actionCreator敞掘,就直接調(diào)用bindActionCreator
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// 遍歷對象,然后對每個(gè)遍歷項(xiàng)的 actionCreator 生成函數(shù)楣铁,將函數(shù)按照原來的 key 值放到一個(gè)對象中玖雁,最后返回這個(gè)對象
const keys = Object.keys(actionCreators)
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
}
bindActionCreators的作用就是使用dispatch把a(bǔ)ction creator包裹起來,這樣我們就可以直接調(diào)用他們了盖腕。這個(gè)在平常開發(fā)中不常用赫冬。
- applyMiddleware
最后我們回頭來看一下之前調(diào)到的中間件,
import thunkMiddleware from 'redux-thunk';
// 兩個(gè)reducer
const todos = (state = INIT.todos, action) => {
// ....
};
const filterStatus = (state = INIT.filterStatus, action) => {
// ...
};
const reducers = combineReducers({
todos,
filterStatus
});
// 這是我們平常使用時(shí)創(chuàng)建store
const store = createStore(reducers, state, applyMiddleware(thunkMiddleware));
為了下文好理解這個(gè)放一下redux-thunk的源碼
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;
可以看出thunk返回了一個(gè)接受({ dispatch, getState })為參數(shù)的函數(shù)
下面我們來看一下applyMiddleware源碼分析
export default function applyMiddleware(...middlewares) {
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)
}
// 每個(gè) middleware 都以 middlewareAPI 作為參數(shù)進(jìn)行注入溃列,返回一個(gè)新的鏈劲厌。此時(shí)的返回值相當(dāng)于調(diào)用 thunkMiddleware 返回的函數(shù): (next) => (action) => {} ,接收一個(gè)next作為其參數(shù)
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 并將鏈代入進(jìn) compose 組成一個(gè)函數(shù)的調(diào)用鏈
// compose(...chain) 返回形如(...args) => f(g(h(...args)))听隐,f/g/h都是chain中的函數(shù)對象补鼻。
// 在目前只有 thunkMiddleware 作為 middlewares 參數(shù)的情況下,將返回 (next) => (action) => {}
// 之后以 store.dispatch 作為參數(shù)進(jìn)行注入注意這里這里的store.dispatch是沒有被修改的dispatch他被傳給了next;
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
// 定義一個(gè)代碼組合的方法
// 傳入一些function作為參數(shù)风范,返回其鏈?zhǔn)秸{(diào)用的形態(tài)咨跌。例如,
// compose(f, g, h) 最終返回 (...args) => f(g(h(...args)))
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
} else {
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
}
也就是一個(gè)三級柯里化的函數(shù)硼婿,我們從頭來分析一下這個(gè)過程
// createStore.js
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
也就是說锌半,會變成這樣
applyMiddleware(thunkMiddleware)(createStore)(reducer, preloadedState)
- applyMiddleware(thunkMiddleware)
applyMiddleware接收thunkMiddleware作為參數(shù),返回形如(createStore) => (...args) => {}的函數(shù)寇漫。 - applyMiddleware(thunkMiddleware)(createStore)
以 createStore 作為參數(shù)刊殉,調(diào)用上一步返回的函數(shù)(...args) => {} - applyMiddleware(thunkMiddleware)(createStore)(reducer, preloadedState)
以(reducer, preloadedState)為參數(shù)進(jìn)行調(diào)用。 在這個(gè)函數(shù)內(nèi)部州胳,thunkMiddleware被調(diào)用冗澈,其作用是監(jiān)測type是function的action。
因此陋葡,如果dispatch的action返回的是一個(gè)function亚亲,則證明是中間件,則將(dispatch, getState)作為參數(shù)代入其中腐缤,進(jìn)行action 內(nèi)部下一步的操作捌归。否則的話,認(rèn)為只是一個(gè)普通的action岭粤,將通過next(也就是dispatch)進(jìn)一步分發(fā)惜索。
也就是說,applyMiddleware(thunkMiddleware)作為enhance剃浇,最終起了這樣的作用:
對dispatch調(diào)用的action進(jìn)行檢查巾兆,如果action在第一次調(diào)用之后返回的是function,則將(dispatch, getState)作為參數(shù)注入到action返回的方法中虎囚,否則就正常對action進(jìn)行分發(fā)角塑,這樣一來我們的中間件就完成了。
因此淘讥,當(dāng)action內(nèi)部需要獲取state圃伶,或者需要進(jìn)行異步操作,在操作完成之后進(jìn)行事件調(diào)用分發(fā)的話蒲列,我們就可以讓action 返回一個(gè)以(dispatch, getState)為參數(shù)的function而不是通常的Object窒朋,enhance就會對其進(jìn)行檢測以便正確的處理。
到此redux源碼的主要部分學(xué)習(xí)結(jié)束蝗岖。