想着对 React 生态更深入的学习,况且 React 源码内容也不多的情况,来读一下他的源码内容。
目前 Redux 已经是 TypeScript 版本的源码,我正在阅读的则是4.0.4
版本代码。
很显然,可以看到这和 React 仓库一样,同样使用了 rollup 打包工具,也验证了 rollup 在开发库类仓库的优势,毕竟大厂都在用嘛。
我们先来看到 package.json
:
{
"name": "redux",
"version": "4.0.4",
"main": "lib/redux.js",
"unpkg": "dist/redux.js",
"module": "es/redux.js",
"types": "types/index.d.ts",
}
我省略了大部分内容,可以看到开发团队针对不同使用场景,对外提供包对应的路径,比如使用 TypeScript 时,对应的则是 types/index.d.ts
,或者使用 ES6 模块化引入时则是 es/redux.js
。值得我们学习。
接下来,我们看到源码部分,整个源码学习内容,将围绕着 src
进行, utils
下的内容即一些简单的工具, types
下则是一些类型的定义。
示例代码中我会移除并省略掉一些逻辑代码和 TypeScript 相关的函数重载代码,以及一些我认为不那么重要的代码,即只展现我想要说明的内容。
那么我们先来看到 createStore
函数吧。
createStore
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 {
// 接受的 reducer
let currentReducer = reducer
// 预加载 state
let currentState = preloadedState as S
// 订阅队列
let currentListeners: (() => void)[] | null = []
// 下一个订阅队列,看起来更像是订阅队列的副本,下面我会来介绍他和订阅队列的不解之谜
let nextListeners = currentListeners
// 检查并创建这个副本的函数
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 拷贝到一个新的数组引用,可以理解为 [...currentListeners] 使得 nextListeners !== currentListeners
nextListeners = currentListeners.slice()
}
}
// 获得 state
function getState(): S {
return currentState as S
}
// 订阅函数
function subscribe(listener: () => void) {
ensureCanMutateNextListeners()
// 操作订阅队列副本,与 currentListeners 之间不受影响
nextListeners.push(listener)
// 返回取消订阅函数
return function unsubscribe() {
ensureCanMutateNextListeners()
// 同样的效果
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
// 看到这里你是不是会想,创建这个副本的目的是什么?
// 官方给到的解释:This prevents any bugs around consumers calling subscribe/unsubscribe in the middle of a dispatch.
}
// dispatch 函数
function dispatch(action: A) {
currentState = currentReducer(currentState, action)
// 遍历通知订阅队列,将 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 {
// 更新 reducer
;((currentReducer as unknown) as Reducer<
NewState,
NewActions
>) = nextReducer
// 同初始化功能一致,即生成 currentState ,下面代码中会提及
dispatch({ type: ActionTypes.REPLACE } as A)
return (store as unknown) as Store<
ExtendState<NewState, StateExt>,
NewActions,
StateExt,
Ext
> &
Ext
}
// 初始化,生成 currentState ,即调用 getState 函数返回的内容, ActionTypes.INIT 即可以理解为私有类型,不会影响到你自己写的 reducer 毕竟里面包含了随机数...
dispatch({ type: ActionTypes.INIT } as A)
// 暴露的 store 对象
const store = ({
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
// 观察者方法这里不做解读,可以理解为对 subscribe 的封装
[$$observable]: observable
} as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
return store
}
那么到此,我们看到了整个 createStore
函数的实现过程以及返回的内容。
这里我来简单的代过 compose
函数,也是业界的常用实现,不是很明白其功能的同学可以简单看到这段代码即可:
(funcs as : Function[]).reduce((a, b) => (...args: any) => a(b(...args)))
接下来,我们来看看 combineReducers
的实现,你可以把这个函数理解为对 reducer
的合并,当然函数名就是这个意思。
combineReducers
export default function combineReducers(reducers: ReducersMapObject) {
const reducerKeys = Object.keys(reducers)
// 用于 combination 函数中使用所需,收集的有效 reducers
const finalReducers: ReducersMapObject = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
// 收集的 keys
const finalReducerKeys = Object.keys(finalReducers)
return function combination(
state: StateFromReducersMapObject<typeof reducers> = {},
action: AnyAction
) {
let hasChanged = false
// 新的 state
const nextState: StateFromReducersMapObject<typeof reducers> = {}
// 遍历 keys
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
// 从遍历中更新 state
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
// 返回 state 或新的 state
return hasChanged ? nextState : state
}
}
在我们看完 reducer
的组合工具函数后,我们来看到绑定执行上下文的语法糖函数 bindActionCreators
。
bindActionCreators
实现代码很少,即绑定执行上下文环境 this
。具体使用场景,可以看到官网 API 文档 bindActionCreators(actionCreators, dispatch) ,那么直接看到源码:
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)
}
const boundActionCreators: ActionCreatorsMapObject = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
最后,我们来看到中间件函数 applyMiddleware
。
applyMiddleware
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('...')
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 对 store.dispatch 进行改写,将其包装在中间件的最后一层,以实现中间件的功能
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
OK, Redux 源码学习完了,是不是没看够,确实!
这才是精妙的库设计,简洁的代码实现强大的功能,那么这次没过瘾没关系,下次我们来品一品 Redux 是如何和 React 结合在一起的吧。
- 本文链接: https://zongzi531.com/2020/03/20/redux%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!