The last time, I have learnedhtml
【THE LAST TIME】 一直是我想寫的一個系列,旨在厚積薄發,重溫前端。前端
也是給本身的查缺補漏和技術分享。git
筆者文章集合詳見:github
範式概念是庫恩範式理論的核心,而範式從本質上講是一種理論體系。庫恩指出:按既定的用法,範式就是一種公認的模型或模式。編程
而學習 Redux
,也並不是它的源碼有多麼複雜,而是他狀態管理的思想,着實值得咱們學習。redux
講真,標題真的是很差取,由於本文是我寫的 redux
的下一篇。兩篇湊到一塊兒,纔是完整的 Redux
。後端
上篇:從 Redux 設計理念到源碼分析api
本文續上篇,接着看 combineReducers
、applyMiddleware
和 compose
的設計與源碼實現數組
至於手寫,其實也是很是簡單,說白了,去掉源碼中嚴謹的校驗,就是市面上手寫了。固然,本文,我也儘可能以手寫演進的形式,去展開剩下幾個 api
的寫法介紹。微信
從上一篇中咱們知道,newState
是在 dispatch
的函數中,經過 currentReducer(currentState,action)
拿到的。因此 state
的最終組織的樣子,徹底的依賴於咱們傳入的 reducer
。而隨着應用的不斷擴大,state
愈發複雜,redux
就想到了分而治之(我寄幾想的詞兒)。雖然最終仍是一個根,可是每個枝放到不一樣的文件 or func
中處理,而後再來組織合併。(模塊化有麼有)
combineReducers
並非 redux
的核心,或者說這是一個輔助函數而已。可是我我的仍是喜歡這個功能的。它的做用就是把一個由多個不一樣 reducer
函數做爲 value
的 object
,合併成一個最終的 reducer
函數。
好比咱們如今須要管理這麼一個"龐大"的 state
:
let state={
name:'Nealyang',
baseInfo:{
age:'25',
gender:'man'
},
other:{
github:'https://github.com/Nealyang',
WeChatOfficialAccount:'全棧前端精選'
}
}
複製代碼
由於太龐大了,寫到一個 reducer
裏面去維護太難了。因此我拆分紅三個 reducer
。
function nameReducer(state, action) {
switch (action.type) {
case "UPDATE":
return action.name;
default:
return state;
}
}
function baseInfoReducer(state, action) {
switch (action.type) {
case "UPDATE_AGE":
return {
...state,
age: action.age,
};
case "UPDATE_GENDER":
return {
...state,
age: action.gender,
};
default:
return state;
}
}
function otherReducer(state,action){...}
複製代碼
爲了他這個組成一個咱們上文看到的 reducer
,咱們須要搞個這個函數
const reducer = combineReducers({
name:nameReducer,
baseInfo:baseInfoReducer,
other:otherReducer
})
複製代碼
因此,咱們如今本身寫一個 combineReducers
function combineReducers(reducers){
const reducerKeys = Object.keys(reducers);
return function (state={},action){
const nextState = {};
for(let i = 0,keyLen = reducerKeys.length;i<keyLen;i++){
// 拿出 reducers 的 key,也就是 name、baseInfo、other
const key = reducerKeys[i];
// 拿出如上的對應的 reducer: nameReducer、baseInfoReducer、otherReducer
const reducer = reducers[key];
// 去除須要傳遞給對應 reducer 的初始 state
const preStateKey = state[key];
// 拿到對應 reducer 處理後的 state
const nextStateKey = reducer(preStateKey,action);
// 賦值給新 state 的對應的 key 下面
nextState[key] = nextStateKey;
}
return nextState;
}
}
複製代碼
基本如上,咱們就完事了。
關於 reducer
更多的組合、拆分、使用的,能夠參照我 github
開源的先後端博客的 Demo:React-Express-Blog-Demo
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S
export type ReducersMapObject<S = any, A extends Action = Action> = {
[K in keyof S]: Reducer<S[K], A>
}
複製代碼
定義了一個須要傳遞給 combineReducers
函數的參數類型。也就是咱們上面的
{
name:nameReducer,
baseInfo:baseInfoReducer,
other:otherReducer
}
複製代碼
其實就是變了一個 state
的 key
,而後 key
對應的值是這個 Reducer
,這個 Reducer
的 state
是前面取出這個 key
的state
下的值。
export default function combineReducers(reducers: ReducersMapObject) {
//獲取全部的 key,也就是將來 state 的 key,同時也是此時 reducer 對應的 key
const reducerKeys = Object.keys(reducers)
// 過濾一遍 reducers 對應的 reducer 確保 kv 格式麼有什麼毛病
const finalReducers: ReducersMapObject = {}
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]
}
}
// 再次拿到確切的 keyArray
const finalReducerKeys = Object.keys(finalReducers)
// This is used to make sure we don't warn about the same
// keys multiple times.
let unexpectedKeyCache: { [key: string]: true }
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError: Error
try {
// 校驗自定義的 reducer 一些基本的寫法
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 重點是這個函數
return function combination( state: StateFromReducersMapObject<typeof reducers> = {}, action: AnyAction ) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState: StateFromReducersMapObject<typeof reducers> = {}
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)
// 上面的部分都是咱們以前手寫內容,nextStateForKey 是返回的一個newState,判斷不能爲 undefined
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
// 判斷是否改變,這裏其實我仍是很疑惑
// 理論上,reducer 後的 newState 不管怎麼樣,都不會等於 preState 的
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
複製代碼
combineReducers
代碼其實很是簡單,核心代碼也就是咱們上面縮寫的那樣。可是我是真的喜歡這個功能。
說 applyMiddleware
這個方法,其實不得不說,redux
中的 Middleware
。中間件的概念不是 redux
獨有的。Express
、Koa
等框架,也都有這個概念。只是爲解決不一樣的問題而存在罷了。
Redux
的 Middleware
說白了就是對 dispatch
的擴展,或者說重寫,加強 dispatch
的功能! 通常咱們經常使用的能夠記錄日誌、錯誤採集、異步調用等。
其實關於Redux
的 Middleware
, 我以爲中文文檔說的就已經很是棒了,這裏我簡單介紹下。感興趣的能夠查看詳細的介紹:Redux 中文文檔
state
的時候,記錄下來 修改前的 state
,爲何修改了,以及修改後的 state
。dispatch
發起的,因此這裏我只要在 dispatch
加一層處理就一勞永逸了。const store = createStore(reducer);
const next = store.dispatch;
/*重寫了store.dispatch*/
store.dispatch = (action) => {
console.log('this state', store.getState());
console.log('action', action);
next(action);
console.log('next state', store.getState());
}
複製代碼
如上,在咱們每一次修改 dispatch
的時候均可以記錄下來日誌。由於咱們是重寫了 dispatch
不是。
const store = createStore(reducer);
const next = store.dispatch;
store.dispatch = (action) => {
try {
next(action);
} catch (err) {
console.error('錯誤報告: ', err)
}
}
複製代碼
因此如上,咱們也完成了這個需求。
可是,回頭看看,這兩個需求如何纔可以同時實現,而且可以很好地解耦呢?
想想,既然咱們是加強 dispatch。那麼是否是咱們能夠將 dispatch 做爲形參傳入到咱們加強函數。
const exceptionMiddleware = (next) => (action) => {
try {
/*loggerMiddleware(action);*/
next(action);
} catch (err) {
console.error('錯誤報告: ', err)
}
}
/*loggerMiddleware 變成參數傳進去*/
store.dispatch = exceptionMiddleware(loggerMiddleware);
複製代碼
// 這裏額 next 就是最純的 store.dispatch 了
const loggerMiddleware = (next) => (action) => {
console.log('this state', store.getState());
console.log('action', action);
next(action);
console.log('next state', store.getState());
}
複製代碼
因此最終使用的時候就以下了
const store = createStore(reducer);
const next = store.dispatch;
const loggerMiddleware = (next) => (action) => {
console.log('this state', store.getState());
console.log('action', action);
next(action);
console.log('next state', store.getState());
}
const exceptionMiddleware = (next) => (action) => {
try {
next(action);
} catch (err) {
console.error('錯誤報告: ', err)
}
}
store.dispatch = exceptionMiddleware(loggerMiddleware(next));
複製代碼
可是如上的代碼,咱們又不能將 Middleware 獨立到文件裏面去,由於依賴外部的 store
。因此咱們再把 store
傳入進去!
const store = createStore(reducer);
const next = store.dispatch;
const loggerMiddleware = (store) => (next) => (action) => {
console.log('this state', store.getState());
console.log('action', action);
next(action);
console.log('next state', store.getState());
}
const exceptionMiddleware = (store) => (next) => (action) => {
try {
next(action);
} catch (err) {
console.error('錯誤報告: ', err)
}
}
const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
store.dispatch = exception(logger(next));
複製代碼
以上其實就是咱們寫的一個 Middleware
,理論上,這麼寫已經能夠知足了。可是!是否是有點不美觀呢?且閱讀起來很是的不直觀呢?
若是我須要在增長箇中間件,調用就成爲了
store.dispatch = exception(time(logger(action(xxxMid(next)))))
複製代碼
這也就是 applyMiddleware
的做用所在了。
咱們只須要知道有多少箇中間件,而後在內部順序調用就能夠了不是
const newCreateStore = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware)(createStore);
const store = newCreateStore(reducer)
複製代碼
const applyMiddleware = function (...middlewares) {
// 重寫createStore 方法,其實就是返回一個帶有加強版(應用了 Middleware )的 dispatch 的 store
return function rewriteCreateStoreFunc(oldCreateStore) {
// 返回一個 createStore 供外部調用
return function newCreateStore(reducer, initState) {
// 把原版的 store 先取出來
const store = oldCreateStore(reducer, initState);
// const chain = [exception, time, logger] 注意這裏已經傳給 Middleware store 了,有了第一次調用
const chain = middlewares.map(middleware => middleware(store));
// 取出原先的 dispatch
let dispatch = store.dispatch;
// 中間件調用時←,可是數組是→。因此 reverse。而後在傳入 dispatch 進行第二次調用。最後一個就是 dispatch func 了(回憶 Middleware 是否是三個括號~~~)
chain.reverse().map(middleware => {
dispatch = middleware(dispatch);
});
store.dispatch = dispatch;
return store;
}
}
}
複製代碼
解釋全在代碼上了
其實源碼裏面也是這麼個邏輯,可是源碼實現更加的優雅。他利用了函數式編程的compose
方法。在看 applyMiddleware
的源碼以前呢,先介紹下 compose 的方法吧。
其實 compose
函數作的事就是把 var a = fn1(fn2(fn3(fn4(x))))
這種嵌套的調用方式改爲 var a = compose(fn1,fn2,fn3,fn4)(x)
的方式調用。
compose
的運行結果是一個函數,調用這個函數所傳遞的參數將會做爲compose
最後一個參數的參數,從而像'洋蔥圈'似的,由內向外,逐步調用。
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args: any) => a(b(...args))) } 複製代碼
哦豁!有點蒙有麼有~ 函數式編程就是燒腦🤯且直接。因此愛的人很是愛。
compose
是函數式編程中經常使用的一種組合函數的方式。
方法很簡單,傳入的形參是 func[],若是隻有一個,那麼直接返回調用結果。若是是多個,則funcs.reduce((a, b) => (...args: any) => a(b(...args)))
.
咱們直接啃最後一行吧
import {componse} from 'redux'
function add1(str) {
return 1 + str;
}
function add2(str) {
return 2 + str;
}
function add3(a, b) {
return a + b;
}
let str = compose(add1,add2,add3)('x','y')
console.log(str)
//輸出結果 '12xy'
複製代碼
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
applyMiddleware 的源碼最後一行是這個。其實即便咱們上面手寫的 reverse 部分。
reduce 是 es5 的數組方法了,對累加器和數組中的每一個元素(從左到右)應用一個函數,將其減小爲單個值。函數簽名爲:arr.reduce(callback[, initialValue])
因此如若咱們這麼看:
[func1,func2,func3].reduce(function(a,b){
return function(...args){
return a(b(...args))
}
})
複製代碼
因此其實就很是好理解了,每一次 reduce
的時候,callback
的a
,就是一個a(b(...args))
的 function
,固然,第一次是 a
是 func1
。後面就是無限的疊羅漢了。最終拿到的是一個 func1(func2(func3(...args)))
的 function
。
因此回頭看看,redux
其實就這麼些東西,第一篇算是 redux
的核心,關於狀態管理的思想和方式。第二篇能夠理解爲 redux
的自帶的一些小生態。所有的代碼不過兩三百行。可是這種狀態管理的範式,仍是很是指的咱們再去思考、借鑑和學習的。
公衆號【全棧前端精選】 | 我的微信【is_Nealyang】 |
---|---|
![]() |
![]() |