从Vue转React技术栈快一年了,这几天断断续续翻了下Redux(V4.0.4)的源码,还是有点点收获。
调试
由于我们看源码时候,很多时候需要调试,方便我们对流程的梳理,调试源码还是很方便的。只需要在你的项目中node_module中redux包下的package.json修改下引用路径即可。
比如Redux中默认package.json
"main": "lib/redux.js",
"unpkg": "dist/redux.js",
"module": "es/redux.js",
我们在自身项目模块的类型修改main或unpkg、module为src/index.js即可。
目录结构
整体目录结构是很清晰的,index.ts是入口文件,types目录是对应TS类型注解文件,utils目录是工具方法。
index.ts
作为入口文件,主要做了两件事。
- 导出类型注解
export {
CombinedState,
PreloadedState,
Dispatch,
Unsubscribe,
Observable,
Observer,
Store,
StoreCreator,
StoreEnhancer,
StoreEnhancerStoreCreator,
ExtendState
} from './types/store'
// reducers
export {
Reducer,
ReducerFromReducersMapObject,
ReducersMapObject,
StateFromReducersMapObject,
ActionFromReducer,
ActionFromReducersMapObject
} from './types/reducers'
// action creators
export { ActionCreator, ActionCreatorsMapObject } from './types/actions'
// middleware
export { MiddlewareAPI, Middleware } from './types/middleware'
// actions
export { Action, AnyAction } from './types/actions'
- 导出暴露的API
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
createStore.ts
首先看createStore方法,再看之前我们先熟悉一些概念。
- createStore方法会创建一个store、存储状态树。
- 修改store中的状态唯一的方式就是调用store.dispatch()。
- 一个应用我们只应该创建一个store。
- 使用combineReducers可以合并多个reducer为一个reducer。
- reducer: 一个函数:入参(State,Action) => 返回新的状态树。
- preloadedState: 初始时的 state,如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。
- enhancer: store增强器,可选的,可以集成一些第三方的插件。
- 返回的store: 读取state、dispatch(action)、subscribe。
export default function createStore<
S,
A extends Action,
Ext = {},
StateExt = never
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
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.'
)
}
// 处理两个参数情况下:preloadedState为enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
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有效的情况下,这里可以备注下跟applyMiddleware方法一样
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
// 后面流程就是没有enhancer的时候创建store
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// 存储当前store中的reducer
let currentReducer = reducer
// 存储当前的state
let currentState = preloadedState as S
// 用一个数组存储当前的监听器
let currentListeners: (() => void)[] | null = []
// 下一次dispatch时候执行的监听器函数列表
let nextListeners = currentListeners
// 标示当前正在处理dispatch
// 避免的常见场景有:
// reducer里面执行dispatch
// reducer里面执行subscribe、unsubscribe
let isDispatching = false
/**
* 如果在reducer里面执行subscribe、unsubscribe
* 那么会导致对listeners存在共同修改的风险
* 因此currentListeners、nextListeners ensureCanMutateNextListeners
* 就是用来解决这个问题的
* ensureCanMutateNextListeners就是为了拷贝一份数据而已,
* 隔离subscribe、unsubscribe与dispatch操作不同的数据源
*/
function ensureCanMutateNextListeners() {
// 从currentListeners拷贝一份到nextListeners
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* 从状态树读取状态
*/
function getState(): S {
if (isDispatching) {
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.')
}
// subscribe、unsubscribe内也不允许执行dispatch
if (isDispatching) {
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
ensureCanMutateNextListeners()
// 每次在listener回调执行订阅(subscribe)操作的一个新listener
// 不会在此次正在进行的dispatch中调用,因为dispatch操作的是currentListeners
// 它只会在下一次dispatch中调用,因为subscribe、unsubscribe操作的是nextListeners
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()
// 删除
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
// 接受action,生成新的state,执行订阅的回调
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?'
)
}
// reducer里面不允许再次执行dispatch, 会有性能问题 不断渲染listeners
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// combineReducers返回的是一个函数
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// dispatch从currentListeners获取数据操作
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
/**
*
* 多用于动态替换reducer、或者实现热加载使用
*/
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
;((currentReducer as unknown) as Reducer<
NewState,
NewActions
>) = nextReducer
// 手动dispatch: 后面会用新的reducer来初始化state产生新的state
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() {
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
}
}
}
dispatch({ type: ActionTypes.INIT } as A)
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(reducer, state, enhancer)来创建store,那么其实会调用两次createStore。一次带enhancer,一次不带enhancer,后面会解释为什么。
applyMiddleware.ts
讲到applyMiddleware方法,我们需要了解Redux是支持两种方式来扩展。
- middleware中间件
- enhancer增强器
关于Redux的单向数据流,我们可以先看过这样一张图,留个印象。
// 创建store增强器,返回StoreEnhancer
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
// applyMiddleware 返回一个函数。
// 然后我们在回想之前createStore中如下代码。
// 所以如果创建store时候带有enhancer那么会先创建store,然后把store往中间件中传递
/**
* createStore中的源码
* return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
*/
return (createStore: StoreEnhancerStoreCreator) => <S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
// 用传进来的reducer, preloadedState创建store
// 这里调用栈回再次回到createStore函数
const store = createStore(reducer, preloadedState)
// 一个临时dispatch定义
let dispatch: Dispatch = () => {
// 改造之前被调用提示错误
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
// middleware约定的中间件签名 ({ getState, dispatch }) => next => action
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
// 得到所有改造后的中间件函数
// 类似所有中间件函数到了这一步 是这样一个函数:next => action
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose方法的作用是,例如这样调用:
// compose(func1,func2,func3)
// 返回一个函数: (...args) => func1( func2( func3(...args) ) )
// 这里的store.dispatch 就是中间件签名的next
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
// 覆盖store本身的dispatch方法
return {
...store,
dispatch
}
}
}
这里有几个非常重要的概念。
- Redux middleware约定的中间件签名 ({ getState, dispatch }) => next => action
- 理解compose函数的作用
applyMiddleware中如下源码就解释下为什么是这个样子。
// middleware约定的中间件签名 ({ getState, dispatch }) => next => action
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
// 得到所有改造后的中间件函数
// 类似所有中间件函数到了这一步 是这样一个函数:next => action
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose方法的作用是,例如这样调用:
// compose(func1,func2,func3)
// 返回一个函数: (...args) => func1( func2( func3(...args) ) )
// 这里的store.dispatch 就是中间件签名的next
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
// 覆盖store本身的dispatch方法
return {
...store,
dispatch
}
这里我们可以对比redux-thunk源码来理解
function createThunkMiddleware(extraArgument) {
return function({ dispatch, getState }) { // 这是中间件函数本身
//参数是store中的dispatch和getState方法 即来自middlewareAPI传入的
return function(next) { // 这是改造后的中间件函数 即上述的 chain
//参数next是被当前中间件改造前的dispatch 即store.dispatch
return function(action) { // 这改造后的dispatch方法
if (typeof action === 'function') {
// action就是如下示例的 getMenusByRoleId()返回的函数
//如果action是一个函数,就调用这个函数,并传入参数给函数使用
return action(dispatch, getState, extraArgument);
}
//否则调用用改造前的dispatch方法
return next(action);
}
}
}
}
异步action的调用
// 一个异步action创建函数
export function getMenusByRoleId(roleId: string) {
return (dispatch: Dispatch<AnyAction>) => {
return $http
.fetchMainMenu({ roleId })
.then(res => {
const { result } = res
dispatch(updateUserMenus(result && Array.isArray(result) ? result : []))
})
.catch(e => console.error(e))
}
}
// 业务中执行异步action
dispatch(getMenusByRoleId('xxx'))
compose.ts
这个是非常重要的一个工具函数,代码非常简洁,主要利用的是数组的API reduce。
// 把多个函数组合为一个函数调用
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)))
}
光看上述代码有点难理解,我们可以看一个示例。
function compose(...funcs) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
const f1 = (a) => {
console.log("f1" + a);
return "f1" + a;
};
const f2 = (a) => {
console.log("f2" + a);
return "f2" + a;
};
const f3 = (a) => {
console.log("f3" + a);
return "f3" + a;
};
var a = compose(f1, f2, f3)("xyz");
console.log("a answer is: ", a);
/**
*
f3xyz
f2f3xyz
f1f2f3xyz
a answer is: f1f2f3xyz
*/
这样子就很好理解了
compose(func1,func2,func3)
返回一个函数: (...args) => func1( func2( func3(...args) ) )
combineReducers.ts
随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分。
combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。
function getUndefinedStateErrorMessage(key: string, action: Action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
function getUnexpectedStateShapeWarningMessage(
inputState: object,
reducers: ReducersMapObject,
action: Action,
unexpectedKeyCache: { [key: string]: true }
) {
const reducerKeys = Object.keys(reducers)
const argumentName =
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
if (reducerKeys.length === 0) {
return (
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
if (!isPlainObject(inputState)) {
const match = Object.prototype.toString
.call(inputState)
.match(/\s([a-z|A-Z]+)/)
const matchType = match ? match[1] : ''
return (
`The ${argumentName} has unexpected type of "` +
matchType +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
if (action && action.type === ActionTypes.REPLACE) return
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
// 检查reducers里面的reducer接受一个未知的action以及初始化的state
// 是否还是可以返回有效的state
function assertReducerShape(reducers: ReducersMapObject) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
)
}
})
}
export default function combineReducers(reducers: ReducersMapObject) {
// 获取reducers对象的key array
const reducerKeys = Object.keys(reducers)
// 存储有效的reducer
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]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// 缓存重复的key
let unexpectedKeyCache: { [key: string]: true }
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
// 接受assertReducerShape校验的异常Error
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
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)
// 需要特别注意state为undefined的情况
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 存储更新后的state
nextState[key] = nextStateForKey
// 每次循环对比新旧state
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length // 对比有没有新增或者删除reducer
return hasChanged ? nextState : state
}
}
这里我们主要需要了解combineReducers返回的是一个函数。这个函数会随着createStore方法调用而传入。然后在调用dispatch时候而产生新的state。
// combineReducers返回的是一个函数
currentState = currentReducer(currentState, action)
bindActionCreators.ts
在讲述这API之前,我们先看看它在业务中的常见使用。
const mapStateToProps = (state: any) => ({
pageState: state.customerAcPageState
})
const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators(
{
customerAcPageState
},
dispatch
)
export default connect(mapStateToProps, mapDispatchToProps)(ManageCustomAct)
// 业务中直接调用
this.props.customerAcPageState({})
大致意思是:把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。
function bindActionCreator<A extends AnyAction = AnyAction>(
// actionCreator: actor 创建函数
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {
// 返回函数通过dispatch包装,可以直接调用
return function (this: any, ...args: any[]) {
return dispatch(actionCreator.apply(this, args))
}
}
// 把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。
// 同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。
export default function bindActionCreators(
// actionCreators: 一个 action creator,或者一个 value 是 action creator 的对象。
actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
dispatch: Dispatch
) {
// 如果传入一个单独的函数作为 actionCreators,那么返回的结果也是一个单独的函数。
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 = {}
// 遍历对象,挨个取出通过dispatch包装并存储
for (const key in actionCreators) {
// 取出action creator
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
结束完。具体还有一些待梳理,包括react-redux、redux的一些优缺点。
ps: 推荐我的微信公众号:xyz编程日记。