概述
Redux 是 JavaScript 狀態(tài)容器,提供可預測化的狀態(tài)管理方案。其三大原則為:
- 單一數(shù)據(jù)源 => 整個應用的 state 被儲存在一顆 object tree 中,并且這個 object tree只存在于唯一一個 store 中
- state 是只讀的 => 唯一改變 state 的方法就是觸發(fā) action,action 是一個用于描述已發(fā)生事件的普通對象
- 使用純函數(shù)來執(zhí)行修改 => 為了描述 action 如何改變 state tree,需要編寫 reducers
純函數(shù): 即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用
Redux 的核心:
- store => store 是由 Redux 提供的 createStore 生成的,通過 createStore 方法創(chuàng)建的 store 是一個對象
- dispatch => 調度 state 的更新
- reducer => 處理 state 的更新。reducer 就是一個純函數(shù),接收舊的 state 和 action,返回新的 state。指定了應用狀態(tài)的變化如何響應 actions 并發(fā)送到 store 的。
- action => action 是把數(shù)據(jù)從應用傳到 store 的有效載荷,它是 store 數(shù)據(jù)的唯一來源。action 是一個對象,其中包括
type和payload屬性。描述變化 - Provider => 全局注冊,在
<Provider></Provider>內的組件都可拿到 state - connect => 連接組件和 store
自我實現(xiàn)
代碼,這部分主要參照了這個系列的相關文章。相關的邏輯可以參照 commit message
官方文檔
- 變化 vs 異步
- React 把處理 state 中數(shù)據(jù)的問題留給了開發(fā)者,Redux -> 處理 state 中的數(shù)據(jù)
- Redux 試圖讓 state 的變化變得可預測
- reducer 合成 -> 每個 reducer 只負責管理全局 state 中它負責的一部分。每個 reducer 的 state 參數(shù)都不同,分別對應它管理的那部分 state 數(shù)據(jù)。開發(fā)一個函數(shù)作為主 reducer,它調用多個子 reducer 分別處理 state 中的一部分數(shù)據(jù),然后再把這些數(shù)據(jù)合成一個大的單一對象。
- combineReducers API
- store 職責:
- 維持應用的 state
- 提供
getState()方法獲取 state - 提供
dispatch(action)方法更新 state - 通過
subscribe(listener)注冊監(jiān)視器 - 通過
subscribe(listener)返回的函數(shù)注銷監(jiān)聽器
源碼
類型
-
export type Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A) => S;
createStore
類型:Function
-
參數(shù):
- reducer<Reducer>
- 【可選】preloadedState<PreloadedState<S>> => 初始化
state - 【可選】enhancer<StoreEnhancer<Ext, StateExt>> => 增強器,用來擴展
store的功能
返回值:
store<Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext>
源碼分析

- 1 - 10:
preloadedState和enhancer不能同時為Function - 12 - 15:如果
preloadedState為Function但是enhancer為undefined那么將preloadedState賦給enhancer,并且preloadedState置為undefined

- 1 - 10:如果
enhancer不為undefined,那么enhancer必須為Function,如果是Function直接返回enhancer(createStore)(reducer, preloadedState)。這個在applyMiddleware時具體講解
// TODO: 使用一個 enhancer 作為例子
- 12 - 14: 如果
reducer不是Function則拋錯

-
currentReducer=> 傳入的reducer -
currentState=> preloadedState => undefined | 用戶傳入的state -
currentListeners: (() => void)[] | null=> [] => 存儲更新函數(shù)的數(shù)組 -
nextListeners=> [] => 下次dispatch將會觸發(fā)的更新函數(shù)數(shù)組 -
isDispatching<boolean>=>Lock,當前是否在dispatch

定義 ensureCanMutateNextListeners、getState、subscribe、dispatch、replaceReducer 和 observable 函數(shù)

- 2:
dispatch一個ActionTypes.INIT的action=> 使得每個reducer返回初始state - 4 - 11: 創(chuàng)建
store變量并返回
dispatch

- 2 - 4:判斷
action是否是純粹的對象。即不是Array|Function|null,isPlainObject 定義 - 6 - 8:
action必須有type - 10 - 12:如果當前正在
dispatch則不能dispatch=> 不可以同時dispatch兩個action - 14 - 19:執(zhí)行
currentReducer,傳入currentState和action - 21 - 25:將
nextListeners賦給currentListeners和listeners,之后循環(huán)遍歷執(zhí)行listener
最終return action
getState

getState 方法比較簡單,就是 return currentState
subscribe

- 2 - 8:如果
listener不是Function或者isDispatching === true拋錯 - 10:定義??
isSubscribed=> 是否已經(jīng)訂閱 - 12: 執(zhí)行
ensureCanMutateNextListeners函數(shù) - 13:將
listener推入nextListeners - 15:
return unsubscribe函數(shù) - 16 - 20:如果
isSubscribed === false直接返回, 如果isDispatching === true拋錯 - 22:關??
- 24:執(zhí)行
ensureCanMutateNextListeners函數(shù) - 25 - 27:在
nextListeners中刪除listener函數(shù),并將currentListeners置為null
ensureCanMutateNextListeners

對 currentListeners 做了淺拷貝以便于可以在 dispatch 的時候使用 nextListeners 作為臨時的 listener。這樣可以防止使用者在 dispatch 的時候調用 subscribe/unsubscribe 出現(xiàn) bug。避免相互影響
combineReducers
- 類型:Function
- 參數(shù):reducers
- 返回值:Function
- 使用
combineReducers例子
源碼分析

- 2 - 13:將用戶傳入的
reducers做一個shallow copy,并且剔除不是Function的reducer。-
finalReducers為有效的reducer -
finalReducerKeys=> 有效reducer的key
-
- 15 - 20:執(zhí)行
assertReducerShape函數(shù),如有Error將Error賦值給shapeAssertionError - 22:返回
combination函數(shù)
assertReducerShape

遍歷給到的 reducer
- 如果在初始化
action時返回undefined,拋錯 - 如果執(zhí)行一個隨機
action時返回undefined,拋錯
combination

- 2 - 4:如果
assertReducerShape函數(shù)執(zhí)行時出錯,則拋出錯誤 - 6 - 7: 定義
hasChanged?? 和nextState變量 - 8 - 19:遍歷
finalReducerKeys數(shù)組,previousStateForKey = state[key],nextStateForKey = reducer(previousStateForKey, action),如果nextStateForKey為undefined拋錯。將reducer計算出來的state存入nextState中。并且判斷previousStateForKey和nextStateForKey是否改變了 - 20 - 21:判斷
finalReducerKeys和傳入state的key的length是否改變。最終return hasChanged ? nextState : state
compose
- 類型:Function
- 參數(shù):Function[]
- 返回值:Function
- 使用
compose例子
源碼分析

- 2 - 5:如果數(shù)組為空,返回一個空函數(shù)
- 7 - 9:如果數(shù)組的
length為1,則返回第一個函數(shù) - 11:對函數(shù)數(shù)組進行
reduce操作compose(increase, square, add) // (...args) => increase(sqpare(add(...args)))
注意:Array.reduce() 的使用 => 回調函數(shù)第一次執(zhí)行時,accumulator 和 currentValue 取值有兩種情況:如果調用 reduce() 時提供了 initialValue,accumulator 取值 initialValue,currentValue 取數(shù)組中的第一個值;如果沒有提供 initialValue,那么 accumulator 取數(shù)組中第一個值,currentValue 取數(shù)組中的第二個值

applyMiddleware
- 類型:Function
- 參數(shù):Middleware[]
- 返回值:Function
- Middleware:
({getState}) => next => dispatch=>({getState}) => dispatch => action => any
applyMiddleware的功能:改造dispatch函數(shù),產(chǎn)生真假dispatch,而中間件就是運行在假真(dispatchAndLog假和next真)之間的代碼。
源碼分析

applyMiddleware 函數(shù),直接返回一個函數(shù),我們直接看這個函數(shù)即可。這個函數(shù)的結構是 (createStore) => (reducer, preloadedState?) => store
- 6:執(zhí)行
createStore函數(shù),獲取store - 7 - 12:定義
dispatch函數(shù) - 14 - 17:定義
middlewareAPI對象,這里middlewareAPI只有兩個屬性,getState和dispatch - 18:將用戶傳入的
middleware[]依次執(zhí)行,并將middlewareAPI作為參數(shù)傳給各個Middleware,Middleware的例子如下
此時function logger({ getState }) { return next => action => { console.log('will dispatch', action) const returnValue = next(action) console.log('state after dispatch', getState()) return returnValue } }chain的值為Middleware返回值的數(shù)組// chain => [middleware1, middleware2, middleware3] next => action => { console.log('will dispatch', action) const returnValue = next(action) console.log('state after dispatch', getState()) return returnValue } - 19:
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)。使用compose函數(shù),此時Middleware中的next === store.dispatch - 21 - 24:
return store,這里的dispatch是 19 行的dispatch
之后看一下 Middleware 使用場所。是作為 applyMiddleware 的參數(shù)使用。applyMiddleware 做為 createStore 的第三個參數(shù)傳入 createStore。

此時的 enhancer === applyMiddleware,之后在 createStore 里面直接執(zhí)行
applyMiddleware,將 createStore 和 reducer + preloadedState 傳入 applyMiddleware,即在 applyMiddleware 代碼中的第 21 行 - 第 24 行的返回值即為 createStore 的返回值
applyMiddleware 做了什么
- 執(zhí)行傳入的
Middleware,并將state作為參數(shù)傳入Middleware - 更新
dispatch,這個更新后的dispatch是用戶傳入的(強化的dispatch),例如上述的logger例子action => { console.log('will dispatch', action) // 此處的 next === dispatch === store.dispatch === createStore(reducer, preloadedState).dispatch const returnValue = next(action) console.log('state after dispatch', getState()) return returnValue }
注意點
-
enhancerstore 的增強器。enhancer是一個高階函數(shù),返回值是一個經(jīng)過包裝的強化的store。applyMiddleware就是一個enhancer
疑問
Redux 做了什么?
- 存儲 state
- 增刪改查 state => 可以理解為 state 的變化
- 觸發(fā)更新
middleware 的執(zhí)行順序
Redux 的中間件模型類似于 koa。在 next 前面以及 next,由外向內依次執(zhí)行。當最里面的 next 執(zhí)行完成之后,next 后面的代碼會由內向外執(zhí)行。非常類似于 Koa 的洋蔥中間件模型。
dispatch 之后,Redux 是如何去處理的?
- 執(zhí)行
reducer更新currentState - 依次執(zhí)行
listener函數(shù)
state 中的數(shù)據(jù)被修改之后,訂閱者們如何去收到更新后的數(shù)據(jù)?
在 subscribe 中通過 store.getState() 獲取數(shù)據(jù)
applyMiddleware 中 dispatch 為何賦值兩次
第一次賦值表示 => 在 Middleware 創(chuàng)建時,不能進行 dispatch
第二次賦值表示 => 此 dispatch 是一個強化后的 dispatch
middlewareAPI 中的 dispatch 什么時候會被調用
注意使用場景
function logger({ getState, dispatch }) {
// 此時的 dispatch 為拋錯函數(shù),即第一次賦值的函數(shù),即第 7 - 12 行
return next => {
// 此處的 next === store.dispatch
// 此時的 dispatch === 下面 return 的函數(shù)
return action => {
console.log('will dispatch', action)
const returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
}
}
middlewareAPI 中的 dispatch 為啥要用匿名函數(shù)包裹
let dispatch = () => console.log('Error!');
const obj = {
name: 'dispatch',
dispatch,
}
obj.dispatch();
dispatch = () => console.log('Ready!');
obj.dispatch();
上述的 log 是什么結果呢?

let dispatch = () => console.log('Error!');
const obj = {
name: 'dispatch',
dispatch: (...args) => dispatch(...args),
}
obj.dispatch();
dispatch = () => console.log('Ready!');
obj.dispatch();
上述的 log 是什么結果呢?

store.subscribe 為啥要有?? isDispatching
dispatch 執(zhí)行時候會循環(huán)執(zhí)行更新函數(shù),要保證 listeners 數(shù)組在這時候不能被改變
以下代碼作用

答:有了這一層判斷,我們就可以這樣傳:createStore(reducer, initialState, enhancer) 或者這樣:createStore(reducer, enhancer),其中 enhancer 還會是 enhancer。
Redux 優(yōu)勢
- 純函數(shù),做測試的時候 easy