Redux 是一个前端状态容器,提供可预测的状态管理,由 Flux 演变衍生而来。 Redux 的一些核心概念和使用方法可以参考官方文档:redux.js.org/
OK,话不多说,直接开始。
阅读版本: 4.0.4
源码地址: github.com/reduxjs/red…
辅助工具: Webstorm、Xmind、Terminal
PS: Redux 源码使用 Typescript 编写,涉及到比较繁琐的类型扩展,泛型等,在解析代码中会适当省略掉,保持简洁。
贴在前面
Xmind 确实是一个好用的工具。

工具函数
先阅读工具函数,因为这是在其他的核心函数模块中会用到的东西,而且工具函数的实现方式与其他模块没有任何强关联关系,可以独立阅读。
actionsTypes
actionTypes 定义了一些 redux 内部使用的 action 类型,供 redux 内部使用。如未识别的 action、初始化状态、替换状态。这些类型是不能用在日常开发过程中的
const randomString = () =>
Math.random()
.toString(36)
.substring(7)
.split('')
.join('.')
const ActionTypes = {
INIT: `@@redux/INIT${randomString()}`,
REPLACE: `@@redux/REPLACE${randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
如上定义了 INIT、REPLACE、PROBE_UNKNOWN_ACTION 三种类型。通过使用
【
@@redux前缀】+【名字】+【随机数36 进制的前7位并使用.分割】
规则生成的内部使用的 action 类型,比如使用随机生成的 PROBE_UNKNOWN_ACTION 去断言测试传入的 reducer 是否正确处理未识别的 action。
isPlainObject
判断数据是不是一个普通对象
function isPlainObject(obj: any): boolean {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
这个函数可以说成通过判断原型链的长度来判断是不是个原生对象。使用了 Object.getPrototypeOf 这个 API。这个 API 等同于已经废弃的 __proto__ 属性。做了以下三步操作:
- 使用迭代深度遍历对象的原型链,找到最后的普通原型对象 S
- 获取 obj 的原型对象,A
- 判断 S === A
因为对于 JavaScript 普通对象而言,它的原型链只有一层,就是 Object。可以理解为对于普通对象 obj 满足:Object.getPrototypeOf(obj) === Object.prototype 且 Object.getPrototypeOf(Object.prototype) === null
warning
一个简单的发警告消息的方法
export default function warning(message: string): void {
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message)
}
try {
throw new Error(message)
} catch (e) {}
}
基本模块
createStore
顾名思义这个函数就是用来创建 store 的一个函数模块。由于这个函数本身包含几个部分以及一些函数体内的闭包,因此我拆成多个部分单独解释。
参数处理
参数由三个部分组成:
- reducer reducer 集合
- preloadedState 初始化 state
- enhancer 这是一个缺省参数,必须是函数。用来自定义强化 createStore 方法,返回一个新的 createStore 方法。
// 如果第二个参数是函数,表示这个参数是 enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}
// 如果有 enhancer,则使用 enhancer 增强 createStore
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
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.')
}
内部数据
内部声明保存的数据
// 保存 state 和 reducer。state 经过处理会改变,reducer 不变
let currentReducer = reducer
let currentState = preloadedState as S
// 订阅消息用的监听
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
// 标记是否正在 dispatch 调用过程中(准确说应该是 reducer 计算过程)。在dispatch 函数中会对这个值进行处理。在内部很多地方会使用这个变量去判断当前的状态。
let isDispatching = false
内部函数
- getState
获取 state 的方法。
function getState(): S {
return currentState as S
}
这个方法只是简单的返回当前的状态(当然还有判断 isDispatching 被我省略了)
- subscribe subscribe 用来添加一个数据变化的监听器。
function subscribe(listener: () => void) {
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
subscribe 函数的主要功能:
- 向 nextListeners 中添加 listener
- 返回一个方法从 nextListeners 中移除添加的 listener
除此之外,使用了一个 ensureCanMutateNextListeners 方法来避免在 dispatch 过程中进行订阅/取消订阅调用的一些问题。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
创建了一个 nextListeners 的浅层副本。相当于说,每次 dispatch 执行的 listener 都是之前保存的一份快照。在这个期间发生的订阅和这取消订阅不会影响执行。
- dispatch
dispatch 用来触发 action 更新 state。
function dispatch(action: A) {
try {
// 修改 dispatch 状态
isDispatching = true
// 使用 reducer 处理状态
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
}
dispatch 做的事情非常简单,包括三件:
- 修改 isDispatching 的状态。使用 try-catch-finally,即 isDispatching 仅表示 reducer 函数处理数据的过程。
- 使用 reducer 函数和 action 处理当前的 state
- 执行 subscribe 添加的监听器
- replaceReducer
替换 reducer。PS: 源码中这一段书写的 Typescript 类型扩展太复杂了,直接简写了。
function replaceReducer(nextReducer: Reducer): Store {
// 替换 currentReducer
currentReducer = nextReducer
// 触发一次 REPLACE 的 action
dispatch({ type: ActionTypes.REPLACE } as A)
// 返回 store
return store
}
触发的 REPLACE action 没有额外的操作,只是做了一次记录。在 combineReducer 中也只是开发环境下使用做了一次判断。
- observable
这是一个留给 observable/reactive 库的一个通信或者操作接口。一个参照标准的实现 tc39
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer: unknown) {
function observeState() {
const observerAsObserver = observer as Observer<S>
if (observerAsObserver.next) {
observerAsObserver.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[?observable]() {
return this
}
}
}
需要一个实现了 next 方法的 observer 对象。
最后一点
dispatch({ type: ActionTypes.INIT } as A)
const store = ({
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[?observable]: observable
} as unknown) as Store
return store
在创建 store 的时候会触发一个 INIT 的 action,然后返回创建的 store 对象。
Compose
compose 是一个比较通用的工具函数,用来组合从右到左的一系列单参数函数(最右边的函数可以使多参数)。最终返回一个函数。比如: compose(f, g, h) => (...args) => f(g(h(...args)))
function compose(...funcs: Function[]) {
if (funcs.length === 0) {
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}
使用 reduce 遍历,并将上一次的处理结果返回作为下一次的输入,每次执行返回结果都是一个函数。
applyMiddleware
在讲 createStore 这个模块的时候提到了这个函数的第三个参数 enhancer 用来增强 createStore。 现在要分析的 applyMiddleware 的作用就是创建一个 enhancer 将一系列中间件应用到 redux store 的 dispatch 方法上。
function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer<any> {
return (createStore: StoreCreator) => (reducer: Reducer, ...args: any[]) => {
const store = createStore(reducer, ...args)
// 声明一个空的未处理的dispatch
let dispatch: Dispatch = () => {
// 直接调用会报错
throw new Error(....)
}
// 每个 middleware 函数的参数。
// 这里比较有意思的是 dispatch 这个参数是在 compose(...chain) 这一步完成之后才有实际值。也就是说dispatch 在 middleare 这个外层函数的执行中是无意义的,仅作为参数传递。
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
// 依次执行每个 middleware 并存储结果到 chain
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
// middleware 的执行结果是一个单参数函数。参数就是 dispatch,并返回新的 dispatch 方法。
// 使用 compose 调用 dispatch chain 去增强 dispatch 方法。
const dispatch = compose(...chain)(store.dispatch)
}
// 返回新的 store,仅覆盖 dispatch 方法
return {
...store,
dispatch,
}
}
简单来说 applyMiddleware 就是一个生成器,最终对 dispatch 进行处理增强或者说覆盖原有的 store.dispatch 功能,然后返回新的 store,可以分为一下几个步骤解释:
- applyMiddleware 生成 enhancer
- middleare 生成 dispatch 的增强器
- dispatch 的增强器生成新的 dispatch 方法
因此我们在写 middleware 的时候会是一个多层嵌套的函数结构:
const testMiddleware = ({ getState, dispatch }) => (next) => (action) => {
if (action.type === 'LOG') {
console.log('记录一条日志')
}
next(action)
}
把匿名箭头函数换成一种具名函数的写法,比较容易看懂:
function testMiddleware({ getState, dispatch }) {
// 返回一个 dispatch 创建函数
return function dispatchCreator(oldDispatch) {
// 返回新的 dispatch 函数
return function newDispatch(action) {
if (action.type === 'LOG') {
console.log('记录一条日志')
}
oldDispatch(action)
}
}
}
combineReducers
combineReducers 是一个辅助工具函数,通过对象字面量的方式接收多个 reducer 组合成一个新的 reducer 函数。
function combineReducers(reducers: ReducersMapObject) {
// 第一步先筛选出正确的 reducers 和 reducerKeys
const reducerKeys = Object.keys(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]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// 断言测试你的 reducer 是否正确
let shapeAssertionError: Error
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(
state: StateFromReducersMapObject,
action: AnyAction
) {
if (shapeAssertionError) {
throw shapeAssertionError
}
let hasChanged = false
const nextState: StateFromReducersMapObject = {}
// 遍历所有的 reducer
// 通过 key 使用 reducer 处理对应的 state
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 最终结果为 true
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 最后判断 reducer 中的 key 不一致是否和 state 中的 key 一致,不一致也表示已修改,使用 newState
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
测试你的 reducer 是否正确的断言方法
function assertReducerShape(reducers: ReducersMapObject) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(
undefined,
{ type: ActionTypes.INIT }
)
// 报错。传入 undefined 应该返回一个不是 undefined 的 initialState
if (typeof initialState === 'undefined') {
throw new Error('...')
}
// 报错。警告你不要用私有的 @@redux/ 命名空间 actionType。你应该为任何未知的 actionType 返回 initialState
// PS: 其实就是你在写 reducer 的 switch case 的时候 default 返回 initialState
if (
typeof reducer(
undefined,
{
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}
) === 'undefined'
) {
throw new Error('...')
}
}
combineReducers 解析完成。其实简单也可以很简单的理解:
- 通过 key 组合多个 reducers
- 遍历 reducers 执行每个 reducer 传入对应 key 值的 state
- 通过 key 组合每一个 reducer 修改后的 state
- 返回新的组合后的 state
bindActionCreator
bindActionCreator 也是一个辅助函数,就是一个值是 action creator 的对象和 dispatch 绑定到一起组合成一个值是可以直接调用触发 dispatch action 的对象。
// 绑定单个函数
function bindActionCreator<A extends AnyAction = AnyAction>(
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {
// 返回一个直接调用的函数,
return function(this: any, ...args: any[]) {
return dispatch(actionCreator.apply(this, args))
}
}
// 绑定多个函数组合的 Object
function bindActionCreators(
actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
dispatch: Dispatch
) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
const boundActionCreators: ActionCreatorsMapObject = {}
// 遍历这个 actionCreators 对象依次调用
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
这个函数的实现比较简单。其原理就是实现一个新的函数把 actionCreator 的执行结果传入 dispatch 执行。
总结
自此,redux 源码解析结束。第一次发,有问题请大佬斧正o(╥﹏╥)o。
消耗: 2杯咖啡、一天 + 一个没睡够的晚上。
接下来会继续解读一下 react-redux 的源码。