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原始碼分析
- react-redux原始碼分析及實現原型
- react-redux原始碼分析及實現原型(下)
下週會給大家帶來redux-saga這個中介軟體原始碼解讀,感興趣的請關注~