redux原始碼分析
Redux is a predictable state container for JavaScript apps.
官網第一句就很全面的介紹了redux。一個可預測的狀態管理工具。redux 是如何做到的呢?
- 單一的資料來源 (states)
- states是隻讀且不可變化的 (每次改變返回新值)
- 通過純函式改變states (reducer, 無副作用, 相同的輸入就有相同的輸出)
- 改變states方式具有可描述性且單一 (action)
資料流

這個大家都很熟悉了吧,就不再講了
原始碼解讀
createStore
func createStore(reducer, preloadedState, enhancer) -> ({ dispatch, subscribe, getState, replaceReducer,[$$observable]: observable })
export default function createStore(reducer, preloadedState, enhancer) { // preloadedState 可以不傳,確定真實的引數 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { return enhancer(createStore)(reducer, preloadedState) } let currentReducer = reducer let currentState = preloadedState let currentListeners = [] let nextListeners = currentListeners let isDispatching = false function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } function getState() { return currentState } function subscribe(listener) { let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } } function dispatch(action) { try { isDispatching = true 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 } function replaceReducer(nextReducer) { currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) } function observable() { //... } dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable, } } 複製程式碼
為了篇幅減少些,上面的程式碼我刪掉了部分錯誤檢查。其實都很好理解,就有一點需要注意一下,為什麼要用兩個變數(currentListeners,nextListeners)來儲存listener。這是因為redux允許在subscirbe中執行unsubscribe。 例如:
const unsubscribe1 = store.subscribe(() => { unsubscribe1() }) const unsubscribe2 = store.subscribe(() => { unsubscribe2() }) dispatch(unknownAction); 複製程式碼
如果不快取dispatch的listener的話 那麼在dispatch裡迴圈listeners時就會有問題。 另外也可以發現,如果你綁定了多個subscribe函式,即使在第一個subscription裡執行了所有的unSubscribe,subscription還是會全部執行一遍 另外 observable
是為了和其他一些observable庫配合使用,當目前為止還沒用過。
applyMiddleware
用法
const logger = ({ getState }) => next => action => { console.log('will dispatch logger1', action) const returnValue = next(action) console.log('state after dispatch logger1', getState()) } const logger2 = ({ getState }) => next => action => { console.log('will dispatch logger2', action) const returnValue = next(action) console.log('state after dispatch logger2', getState()) } const store = createStore( todos, preload, applyMiddleware(logger1, logger2), ); // will dispatch logger1 // will dispatch logger2 // state after dispatch logger2 // state after dispatch logger1 複製程式碼
原始碼
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) } // chainItem:next => action => {...} const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } 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))) } // compose(logger1, logger2) => (...arg) => logger1Next(logger2Next(...arg)) // 合成之後的dispatch就成了這個樣子: const dispatch = (action) => logger1Next(logger2Next(store.dispatch))(action); // 假如還有logger3, 那麼應該是這個樣子 const dispatch = (action) => logger1Next(logger2Next(logger3Next(store.dispatch)))(action); 複製程式碼
可以compose原因是middle模式統一:store => next => action => {} 在執行完 const chain = middlewares.map(middleware => middleware(middlewareAPI))之後 chainItem: next => action => {} 本質是 接收一個dispatch,再返回一個合成的dispatch
bindActionCreators
用法
bindActionCreators({ actionCreator1, actionCreator2, ...}, dispatch) => ({ boundAction1, boundAction2, ... })
原始碼
// 返回 boundAction function bindActionCreator(actionCreator, dispatch) { return function() { return dispatch(actionCreator.apply(this, arguments)) } } export default function bindActionCreators(actionCreators, dispatch) { // 僅僅傳入一個actionCreator if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error() } // 傳入一個物件時,繫結所有key,並返回一個物件 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 } 複製程式碼
combineReducers
由於redux僅有一個store,所以當專案複雜的時候,資料需要分類,這時就會用到此函式。作用是將多個分開的reducers合併。
原型:(reducer1,reducer2, reducer3,...) => combinedReducer
export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} // 遍歷檢查 reducer 不應該為 undefined 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}"`) } } if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} } let shapeAssertionError try { // 檢查 reducer 必須設定初始值,不可以返回 undefined assertReducerShape(finalReducers) } catch (e) { shapeAssertionError = e } // combinedReducer return function combination(state = {}, action) { if (shapeAssertionError) { throw shapeAssertionError } if (process.env.NODE_ENV !== 'production') { // 檢查 state 是否合法,例如state必須是物件、state應該有相對應的reducer等 // 使用了combineRudcer之後,state就是物件了,原來的state都放在了相對應的key下面 // 例如:combineReducers({ todos: reducer1, todoType: reducer2 }); // store 變成了 { todos: todoState, todoType: todoTypeState }; const warningMessage = getUnexpectedStateShapeWarningMessage( state, finalReducers, action, unexpectedKeyCache ) if (warningMessage) { warning(warningMessage) } } let hasChanged = false const nextState = {} // 將action分發給每個reducer, 如果該改變就返回新的。 // 否則返回舊值,類似於你在每個reduer中的做法。 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) if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } } 複製程式碼
總結
redux原始碼還是比較好理解的,記住reducer一定要保證是純函式。這對於測試和與其他的庫配合至關重要。例如 react-redux。 感興趣的朋友可以看看我的react-redux原始碼分析
- ofollow,noindex">react-redux原始碼分析及實現原型
- react-redux原始碼分析及實現原型(下) 下週會給大家帶來redux-saga這個中介軟體原始碼解讀,感興趣的請關注~