Redux 原理分析 Redux 是一个管理全局应用状态的库,redux 可以理解为是一个上帝视角,包含了应用所需要的东西,发放给需要的组件,redux 更新了也会去通知对应的组件。
回顾 redux 的使用 学习原理之前,肯定需要熟练的应用,这样才能更好的理解原理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function xxReducer (state = { name: "chenjiang" }, action ) { switch (action.type ) { case "add" : return state; ... default : return state; } }const store = createStore (xxReducer); store.dispatch ({type : 'add' })
几大核心概念:
Store :正如其名“仓库”,它存储了所有的状态,并且提供操作它的 API。Action :触发一个动作,告诉 redux,我将要处理什么逻辑,但是真正的逻辑并不是它去做,而是交给 reducer。Reducder :接收到 Action 指令,找到对应的处理逻辑,处理完成后更新 Store 状态。
以下对原理剖析。
createStore redux 版本:v5.0.1
先从createStore
入口出发,我们简化一下源码,大致结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 export function createStore (reducer, preloadedState, enhancer ) { if (typeof enhancer !== "undefined" ) { return enhancer (createStore)(reducer, preloadedState); } let currentReducer = reducer; let currentState = preloadedState; let currentListeners = new Map (); let nextListeners = currentListeners; let listenerIdCounter = 0 ; function ensureCanMutateNextListeners ( ) {} function getState ( ) {} function subscribe (listener ) {} function dispatch (action ) {} function replaceReducer (nextReducer ) {} dispatch ({ type : ActionTypes .INIT }); const store = { dispatch : dispatch, subscribe, getState, replaceReducer, }; return store; }
ensureCanMutateNextListeners 拷贝一份监听器数组 主要作用是防止监听器数组被错误修改,函数逻辑其实很简单,就是把当前currentListeners
监听器数组拷贝一份给nextListeners
。
不经有疑问为什么要多此一举呢?主要是和 Redux 订阅和通知的机制有关系。
当我们调用 dispatch ,如果此时又调用 subscribe、unsubscribe,那么就会有意外问题,比如:
在 dispatch 后,redux 会遍历监听器数组,调用监听器过程中也存在添加或者移除监听器的操作,可能本次 dispatch 中遗漏了一些监听器的调用。 如果在多个地方使用同一个监听器数组的引用,对数组的修改会影响到其它地方。因此最好确保是一个独立的副本,以避免不必要的副作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function ensureCanMutateNextListeners ( ) { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice (); } }function ensureCanMutateNextListeners ( ) { if (nextListeners === currentListeners) { nextListeners = new Map (); currentListeners.forEach ((listener, key ) => { nextListeners.set (key, listener); }); } }
版本 5 以后为什么采用 Map 没有用原来的 slice?
因为 Map 的好处它允许我们更方便的添加、删除、以及查找监听器,并且性能比直接操作数据更高效。Map 的查找和删除操作时间复杂度是 O(1),而数组的查找和删除操作是 O(n);
getState 获取最新的状态 返回最新的状态
1 2 3 function getState ( ) { return currentState; }
subscribe 订阅回调 调用订阅函数,返回一个取消订阅函数,形成闭包,那么isSubscribed
就一直被保留,如果重复订阅,也就会被阻止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function subscribe (listener ) { let isSubscribed = true ; ensureCanMutateNextListeners (); const listenerId = listenerIdCounter++; nextListeners.set (listenerId, listener); return function unsubscribe ( ) { if (!isSubscribed) { return ; } isSubscribed = false ; ensureCanMutateNextListeners (); nextListeners.delete (listenerId); currentListeners = null ; }; }
dispatch 触发更新指令 接收更新指令,交给 reducer,reducer 处理完成后,通知所有订阅者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function dispatch (action ) { try { isDispatching = true ; currentState = currentReducer (currentState, action); } finally { isDispatching = false ; } const listeners = (currentListeners = nextListeners); listeners.forEach ((listener ) => { listener (); }); return action; }
replaceReducer 动态替换 reducer 在运行时动态替换 store 中的 reducer
应用场景:
例如不同角色,处理同一个功能,存在不同的处理逻辑,此时就需要根据登录的用户来切换 reducer 来处理不同的逻辑。 动态加载 reducer:为了优化性能和加载时间,常常会将 reducer 按需加载,在需要的时候再来加载。
1 2 3 4 function replaceReducer (nextReducer ) { currentReducer = nextReducer; dispatch ({ type : ActionTypes .REPLACE }); }
combineReducers 合并多个 reducer 在整个应用中,不同的模块会有不同的处理逻辑,因此需要拆分成多个 reducer 来处理独立逻辑,但是 createStore 只能传入一个 reducer,这时就需要传之前将多个 reducer 合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 const rootReducer = combineReducers ({ login : loginReducer, home : homeReducer, });createStore (rootReducer);export default function combineReducers (reducers ) { 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); return function combination (state = {}, action ) { let hasChanged = false ; const nextState = {}; 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); nextState[key] = nextStateForKey; hasChanged = hasChanged || nextStateForKey !== previousStateForKey; } hasChanged = hasChanged || finalReducerKeys.length !== Object .keys (state).length ; return hasChanged ? nextState : state; }; }
applyMiddleware 使用中间件 这个 API 主要是为了接入 Redux 生态,例如接入 redux-logger、redux-thunk 等,通俗来讲这个函数是 redux 功能的加强器(enhancer
)。
整体剖析这个applyMiddleware
,调用applyMiddleware
返回一个函数(可以理解为加强功能的函数),我们在 createStore 源码中可以看到这一行enhancer(createStore)(reducer, preloadedState)
,enhancer(createStore)
就可以理解为加强 createStore 功能,返回一个加强后的 createStore(Plus 版本),再次调用就等同于 createStorePlus(reducer, preloadedState)
。
下面再来看看细节,它是如何加强的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 createStore (rootReducer, applyMiddleware (thunkMiddleware, loggerMiddleware));export default function applyMiddleware (...middlewares ) { return (createStore ) => (reducer, preloadedState ) => { const store = createStore (reducer, preloadedState); let dispatch = ( ) => {}; const middlewareAPI = { getState : store.getState , dispatch : (action, ...args ) => dispatch (action, ...args), }; const chain = middlewares.map ((middleware ) => middleware (middlewareAPI)); dispatch = compose (...chain)(store.dispatch ); return { ...store, dispatch, }; }; }
有和没有 compose 处理,有什么不同呢?
1 2 3 4 5 6 7 8 9 10 applyMiddleware (thunkMiddleware, loggerMiddleware);loggerMiddleware ();thunkMiddleware ();thunkMiddleware (loggerMiddleware ());
compose 将多个 middleware 产生联系 也就是上面提到,前面中间的中间件执行结果,最为后面中间件的执行基础.
先来分析,我们需要什么的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let num = 1 ;const fn1 = (n ) => { return n + 1 ; };const fn2 = (n ) => { return n + 2 ; };const fn3 = (n ) => { return n + 3 ; };const total = fn1 (fn2 (fn3 (num)));const total1 = compose (fn1, fn2, fn3)(num);
compose 内部是如何处理的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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)) ); }
总结
Redux 就是一个发布订阅模式,有数据更新了就通知所有订阅者。
dispatch 并不是真正的去处理逻辑,而是触发更新指令,然后交给 reducer 去处理逻辑。
applyMiddleware 返回的是一个加强器,类似装饰器模式,调用加强器传入 createStore,返回一个加强版 createStore,也返回了一个提供了一个加强版 dispatch 函数。
遍历监听器数组,执行回调时候也有可能发生订阅或者取消订阅,这也就是源码换种频繁使用ensureCanMutateNextListeners
保证所有订阅者都能收到消息。