当你想要阅读一个项目的源码,感到不知所措,从何开始时。最简单有效的办法是从,项目的入口文件,通常是 index.js 开始读。从入口文件向外暴露的 API 为起点,一步步追根溯源,逐渐理清脉络,算是比较简单容易施行的方法。
这里我们先看下 Redux 的入口文件,以及向外部的暴露的 API
Redux 在某个版本后,使用了 TS 重写整个项目,为了减少本文的长度,展示的代码删除了对类型文件的导入和导出。
// functions
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
通过观察入口文件,我们可以很轻易的看出 redux 的导出,接下来我们会大体上根据重要性来逐一分析每个文件的作用
DO_NOT_USEActionTypes 是通过 ‘./utils/actionTypes’ 导入,我们看下 actionTypes.js 的文件
const randomString = () =>
Math.random().toString(36).substring(7).split('').join('.')
const ActionTypes = {
INIT: `@@redux/INIT${
/* #__PURE__ */ randomString()}`,
REPLACE: `@@redux/REPLACE${
/* #__PURE__ */ randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${
randomString()}`
}
export default ActionTypes
导出的只是一个 Pure Object,这里是 Redux 自己定义的 type 类型,当 Redux 初始化的时候,会自动的 dispatch 的 type 为 ActionTypes.INIT 的 action 对象,这也是为什么 reducer 函数必须默认返回一个 state 对象,Redux 为把这个 state 当作 store 的基础 state 进行初始化,REPLACE 的同理,是进行 replace 操作时 dispatch action 的 type 类型
对于作为使用者的我们来说,这个对象对我们没有任何用处,不需要关心
compose 是函数式编程里的一个概念,在函数式编程里,各种函数秉持着做好一件事的原则,当想完成一件复杂的事情时,会把各种小函数组合成为一个大函数,来完成想要的功能。这种组合,经常以一个函数的输出当作另一个函数的输入的形式出现,据此抽象出了 compose 函数,来帮助进行相应的操作
这里难以理解的是 redux 对于 compose 的实现,为了方便理解,我们先看下 compose 的实际用法,和易于理解的实现
function compose(...funcs) {
if (funcs.length === 0) {
funcs.push(x => x)
}
return function composed(...args) {
let result = funcs.pop()(...args) // 第一次调用
while (funcs.length !== 0) {
const func = funcs.pop()
result = func(result)
}
return result
}
}
const str = 'foo'
const toUpperCase = str => str.toUpperCase() // 把小写字母变成大写
const addAToFront = str => 'a' + str // 在传入的字符串头加个a,然后返回
// const c1 = (...args) => toUpperCase(addAToFront(...args));
const c1 = compose(toUpperCase, addAToFront)
c1(str) // => AFOO
// const c2 = (...args) => addAToFront(toUpperCase(...args));
const c2 = compose(addAToFront, toUpperCase)
c1(str) // => aFOO
通过返回值我们可以观察到,compose 函数就是从右到左,把前一个函数的输出当作后一个函数输入,一直按序调用到最后一个函数,然后返回最终的结果。注意 c1 和 c2 的返回值不同,这是非常值得注意的一点,传入函数的顺序可能会影响最终的结果
接下来我们看看 redux 的 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)))
}
这个函数前面部分很好理解,主要是最后这个 reduce 的操作很酷炫,要理解这步做了什么,只要很简单的用三个函数来展开这个规约式,就一目了然了
const addOne = x => x + 1;
const addTwo = x => x + 2;
const mulTwo = x => x * 2;
// const c = (...args) => addTwo(mulTwo(addOne(...args)));
const c = compose(addTwo, mulTwo, addOne);
c(1) // => 6
// 接下来我们开始刨析整个流程,
const funcs = [addTwo, mulTwo, addOne];
// 首先要要明确的一点 reduce 对 funcs 做了两次规约操作,如果你不知道为什么,你需要去了解下 reduce 函数的运行流程
// 第一次 reduce 操作,对象是 addTwo 和 mulTwo
(a, b) => (...args) => a(b(...args))
// 我们把 a 和 b 换成实际的函数名字
(addTwo, mulTwo) => (...args) => addTwo(mulTwo(...args))
// 最终我们得到了一个中间函数
const temp = (...args) => addTwo(mulTwo(...args));
// 接着第二次 reduce 操作,对象是 temp 和 addOne,我们再次替换上面的 a 和 b
(temp, mulTwo) => (...args) => temp(addOne(...args))
// 此时规约完毕,我们得到了最终函数
const final = (...args) => temp(addOne(...args));
// 此时我们把addOne(...args)替换到temp的参数处就会得到
addTwo(mulTwo(addOne(...args)));
对于 3 个以上的函数是同样的逻辑,有兴趣和时间的读者可以自己试试看
我们先看下代码
function bindActionCreator<A extends AnyAction = AnyAction>(
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {
return function (this: any, ...args: any[]) {
return dispatch(actionCreator.apply(this, args))
}
}
export default function bindActionCreators(
actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
dispatch: Dispatch
) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${
actionCreators === null ? 'null' : typeof actionCreators
}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
const boundActionCreators: ActionCreatorsMapObject = {
}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
理解 bindActionCreators 的关键在于理解 bindActionCreator,而 bindActionCreator 是一个很简单的函数,但为了理解它,我们要先解释几个概念
action 是一个特定结构的对象
const action = {
type: 'Type',
payload: 'any'
}
actionCreator 是帮助我们创建 action 对象的函数
function createSetUser(userData) {
return {
type: 'SetUser',
payload: userData
}
}
在实践中,我们通过 Redux 提供的 dispatch 函数,以 disaptch(createSetUser(userData))的形式通知 Redux 更改数据,当然我们也手写 action,只是不利于维护而已
function bindActionCreator<A extends AnyAction = AnyAction>(
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {
return function (this: any, ...args: any[]) {
return dispatch(actionCreator.apply(this, args))
}
}
我们把自己定义的 actionCreator 函数和 redux 的 dispatch 函数传给 bindActionCreator,bindActionCreator 会返回一个函数,这个函数的参数会原封不动的传给我们的 actionCreator 函数,得到 actionCreator 的结果后,再调用我们传入的 dispatch 函数,并传入 actionCreator 的结果当作参数
可以看出来,actionCreator 作用就是帮我们省了自己 dispatch 的步骤
bindActionCreators 的作用和 bindActionCreator 一样,当传入的 actionCreators 是一个函数时,会直接返回 bindActionCreator 的结果,当是一个对象时,这个对象的 value 必须 actionCreator 函数,然后依次通过 bindActionCreator 对这些函数进行绑定,最后返回一个新的对象,key 值和原来的相同的,但是其 value 是已经经过 bindActionCreator 绑定的 actionCreator 函数
import $$observable from './utils/symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore<
S,
A extends Action,
Ext = {
},
StateExt = never
>(
reducer: Reducer<S, A>, // 普通 reducer 函数
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>, // 初始state或Enhancer
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
// 参数传错了
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function.'
)
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// preloadedState 是一个 function,表示传递的是一个 enhancer
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 在传递了 enhancer 情况下,把 createStore 的函数交给 enhancer,从而进行拦截相关操作
// 实现中间件拦截action的功能,这里暂且不表,等到后面讲 applyMiddleware.ts 时会详细说
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer // 当前的reducer
let currentState = preloadedState as S // 当前store的state
let currentListeners: (() => void)[] | null = [] // subscribe 传递的函数
let nextListeners = currentListeners //
let isDispatching = false // 判断有没有循环调用 disaptch
function ensureCanMutateNextListeners() {
// 保证在增删监听函数时,不会造成额外影响
// 举个例子,在调用监听函数时,如果在监听函数内部,再次调用subscribe,要保证这次传入subscribe的函数
// 不会在本轮调用中调用,同样的即使是unsubscribe,我们也得保证unsubscribe的函数这一轮被调用,下一轮
// 才会应用相关的修改
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function getState(): S {
// 拿到当前store的state
if (isDispatching) {
// 表示你在reducer函数里调用了getState
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState as S
}
function subscribe(listener: () => void) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
// 表示你在reducer函数里调用了subscribe
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
let isSubscribed = true // 防止重复 unsubscribe
ensureCanMutateNextListeners() // 见ensureCanMutateNextListeners定义处
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners() // 见ensureCanMutateNextListeners定义处
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action: A) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
// 传入 currentState, action 拿到新的 state 替换 currentState
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<NewState, NewActions extends A>(
nextReducer: Reducer<NewState, NewActions>
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// TODO: do this more elegantly
;((currentReducer as unknown) as Reducer<
NewState,
NewActions
>) = nextReducer
dispatch({
type: ActionTypes.REPLACE } as A)
// change the type of the store by casting it to the new store
return (store as unknown) as Store<
ExtendState<NewState, StateExt>,
NewActions,
StateExt,
Ext
> &
Ext
}
function observable() {
// observable 相关代码,平常用不上
const outerSubscribe = subscribe
return {
subscribe(observer: unknown) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
const observerAsObserver = observer as Observer<S>
if (observerAsObserver.next) {
observerAsObserver.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return {
unsubscribe }
},
[$$observable]() {
return this
}
}
}
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({
type: ActionTypes.INIT } as A) // 初始化store state
const store = ({
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
return store
}
createStore 可以说是 Redux 的核心函数,但其实现却简单优雅,逻辑上清晰明了,不由得说一声佩服。
值得注意的是,createStore 中并没有包含中间件相关的逻辑,中间件的实现都在 applyMiddleware.ts 中
redux 的实现确实精巧,在和 createStore 实现逻辑完全剥离的情况下,实现了洋葱模型的中间件
import compose from './compose'
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
return (createStore: StoreCreator) => <S, A extends AnyAction>(
reducer: Reducer<S, A>,
...args: any[]
) => {
const store = createStore(reducer, ...args)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
我们将中间件当作参数调用 applyMiddleware 会返回名为 enhancer 的函数,我们会这个函数传给 createStore,createStore 在检测到传入 enhancer 时,会调用 enhancer 函数,并把自身当作参数传入进去。
然后就进入了函数内部的逻辑,首先创建了store,然后构建了中间件可以访问的API对象,然后将API对象传入所有中间件函数,自此,中间件有了拿到store state和dispatch action的能力
然后就是最关键的一步 compose(…chain)(store.dispatch)
,执行后,整个中间件的逻辑完成
为了理解最后一步做了什么,我们先要了解 redux 中间件的形式,这里我们用两个中间件举例子,一个redux-thunk,一个logger
// redux-thunk.js
function createThunkMiddleware(extraArgument) {
return ({
dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
// logger.js
function logger({
getState }) {
return next => action => {
console.log('will dispatch', action)
const returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
}
这里我们搞清楚几个容易误会的点,logger 是一个中间件,createThunkMiddleware不是一个中间件,它返回的函数才是一个Redux中间件
我们来看一下一个中间件的函数形式
const middleware = middlewareAPI => next => action => {
// do something before dispatch
const res = next(action);
// do something after dispatch
}
这里我们来解释下各个参数的意思,然后再弄清楚他们是怎么来的
middlewareAPI就是一个结构如下的对象
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
next是下一个将被调用的中间件函数,我们可以选择调用,直接拦截本次调用
action是传给dispatch的参数,正常情况下是一个object,但可以是任何东西,函数,字符串都可以
现在我们过一遍下面这个代码逻辑,看看中间件们是怎么串联起来和工作的
假设我们以 applyMiddlewares(logger, createThunkMiddleware())
的顺序调用
还注意我们前面说compose的时候,顺序不同可能影响返回的结果
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
首先是传入middlewareAPI,调用每个中间函数,生成如下函数
const reduxThunkTmp = next => action => {
// 注意此时可以访问 middlewareAPI
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
const loggerTmp = next => action => {
// 注意此时可以访问 middlewareAPI
console.log('will dispatch', action)
const returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
然后经过compose后,变成了如下形式
const composed = (...args) => loggerTmp(reduxThunkTmp(...args))
以store.dispatch为参数,调用composed
dispatch = composed(store.dispatch)
// =>
dispatch = loggerTmp(reduxThunkTmp(store.dispatch))
注意此时store.dispatch是reduxThunkTmp的next参数,然后返回了reduxThunkTmp2函数
const reduxThunkTmp2 = action => {
// 注意此时可以访问 middlewareAPI
// 注意此时可以访问 next,其 next 是 store.dispatch
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
还记得compose的原理嘛,reduxThunkTmp2作为reduxThunkTmp的返回值,会被当作loggerTmp的参数,调用loggerTmp(reduxThunkTmp2),生成以下函数
const loggerTmp2 = action => {
// 注意此时可以访问 middlewareAPI
// 注意此时可以访问 next,其 next 是 reduxThunkTmp2
console.log('will dispatch', action)
const returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
此时loggerTmp2就是最终我们的dispatch变量的值
假如我们dispatch的一个action object,让我们来看下流程
loggerTmp2就很好展示redux中间件洋葱模型的特点,我们可以在action经过reducer之间做一些操作,也可以在之后做一些操作。如果把reducer当作洋葱核,中间件当作洋葱皮,我们的action会穿过一层皮两次
试想一下如果我们传入的是一个函数
假设我们以 applyMiddlewares(logger, createThunkMiddleware())
的顺序调用
这里再想一下,如果我们不是以applyMiddlewares(logger, createThunkMiddleware())
的顺序,而是applyMiddlewares(createThunkMiddleware(), logger)
的顺序,当我们传递一个function类型的action时,logger还能接触到这个action吗?
这里总结下redux中间件的执行流程
在不被拦截的情况下,action会根据applyMiddlewaresc参数的顺序,从左到右,依次经过各个中间件,直到调用reducer,再反向依次经过各个中间件
combineReducers 是在使用redux中经常使用的一个函数,我们使用这个函数来分割单一过大的reducer,更好的模块化,其流程很简单,生成一个组合的reducer,这个reducer把接收到action,分发给各个局部reducer,得到他们的state,将这些state合并成为一个整个state,返回这个state
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
export default function combineReducers(reducers: ReducersMapObject) {
const reducerKeys = Object.keys(reducers)
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]
}
}
// 上面是过滤value为undefined的值
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 {
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 // 记录本次state树是否发生了改变
const nextState: StateFromReducersMapObject<typeof reducers> = {
} // 发生改变就会返回新的对象
for (let i = 0; i < finalReducerKeys.length; i++) {
// 遍历每个reducer
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key] // 拿到现在的state
const nextStateForKey = reducer(previousStateForKey, action) // 得到新的state
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey // 复制到nextState上
hasChanged = hasChanged || nextStateForKey !== previousStateForKey // 前后的state不相等,记录发生了变化
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state // 如果发生了改变就返回新的state,否则返回原来的state
}
}
花了几个小时终于把源码读完了,除了对整个项目短小精悍的佩服之外,不由得说redux这个中间件的逻辑实现有点绕,却很巧妙。