前言
看完 Redux 的源码,可谓是收获颇丰,先不论对 Redux 本身。光是其中“中间件模式”的实现就够反复🤔琢磨了,其中 isPlainObject 🔧工具函数为了吃透它来了一场考古。往基础走,Redux 简直就是闭包的大型实现现场,applyMiddleware 中的闭包简直拍案叫绝。
大部分笔记以注释的形式写在了源码文件里,虽然有点碎碎念,但是想来会有所帮助。
📒 代码笔记地址: https://github.com/yingpengsha/redux
目录结构
src
├─ applyMiddleware.js // 组合多个 middleware 生成一个 enhancer
├─ bindActionCreators.js // 一个提供给使用者的工具函数,绑定 dispatch 和 actionCreator
├─ combineReducers.js // 组合多个 reducer 生成一个 reducer
├─ compose.js // 组合多个 enhancer 生成一个 enhancer (一个将一堆函数首尾相连的🔧工具函数)
├─ createStore.js // 接收 reducer[, preloadedState][, enhancer] 生成一个 store
├─ index.js // 暴露接口
└─ utils
├─ actionTypes.js // 生成 redux 库自身所需的 actionType
├─ isPlainObject.js // 判断一个变量是否是普通对象
└─ warning.js // 用于抛出错误的工具函数
createStore
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
// ...各种参数校验
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
// ...参数校验
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function getState() {
// ...如果正在 dispatch,抛错
return currentState
}
function subscribe(listener) {
// ...参数校验,时机检查
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
// ...如果正在 dispatch,抛错
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action) {
// ...各种参数校验,时机检查
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(nextReducer) {
// ...参数校验
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
// ...参数校验
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
- 如果有 enhancer 增强函数则,调用增强后的 createStore 初始化
- 利用闭包的原理,保存各类变量的状态
- 定义各类函数暴露给用户使用
- 初始化
getState()
用于返回 state
- 因为闭包,所以能准确的返回先前保留的 state
subscribe
用于添加监听函数
- 对先前的监听函数集合进行浅拷贝备份
- 然后将新的监听函数填充进去
- 返回一个取消监听的函数
- 浅拷贝备份
- 将监听事件从集合中剔除
dispatch
分发 action
- 将 action 分发到当前的 reducer 中(真正的改值在 reducer 中)
- 遍历监听函数,并执行
- 返回传入的 action (并无太多实际意义)
replaceReducer
重置覆盖 reducer
- 覆盖当前 reducer
- 重新初始化状态
observable
observable 这个函数不是暴露给使用者的,而是提供给其他观察者模式/响应式库的 API
具体可看 https://github.com/tc39/proposal-observable
applyMiddleware
import compose from './compose'
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
来结合一下 redux-thunk 的源码看一下
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;
组合多个 middleware 生成一个 enhancer,中间件会替换 dispatch 给用户使用,而真正的 dispatch 则在中间件的末尾等待最后的处理。
- applyMiddleware 执行后会返回一个 enhancer
- 然后这个 enhancer 会返回一个 dispatch 覆盖原先的 dispatch
- createStore 就是最初始的 createStore 函数
- 定义的 dispatch 定义只是暂时的,最后会被中间件覆盖,目的是告诉中间件: v”正在初始化呢,别🐒急着dispatch!”
相关 Issues: https://github.com/reduxjs/redux/issues/1240 很精彩
- 然后传递 getState,dispatch 给中间件使用
- 倒数第二行覆盖 dispatch 是整个库里最精彩的地方
1⃣️ dispatch 的不简单覆盖
- 上方的 dispatch 覆盖了上上方的报错专用的 dispatch
- 同时利用闭包的属性,覆盖了传到 middleware 里面的 middlewareAPI.dispatch 里的 dispatch
- 这样就能保证在最后的 dispatch 生成之前 dispatch 是报错专用的,生成之后是正常中间件生成用的
2⃣️ 怎么让 action 在中间件之间传递,最后传递到 store.dispatch 手上
- 假设我们有两个中间件 saga, thunk
- 他们都有会生成一个类似 dispatch 的函数,假设为 createSagaDispatch, createThunkDispatch
- 然后 compose(createSagaDispatch, createThunkDispatch)(store.dispatch)
- 等于 createThunkDispatch(createSagaDispatch(store.dispatch))
- 效果 createSagaDispatch(store.dispatch) —> sagaDispatch
createThunkDispatch(sagaDispatch) —> thunkDispatch
- 最后将 thunkDispatch 暴露给使用者
- 执行顺序则是反方向运行回调的函数
- 最后返回意味着暴露给用户的 dispatch 将会被中间件覆盖,而真正的 dispatch 给最里层的中间件用
compose
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)))
}
compass 的作用就是整合多个 enhancer 函数
但实际上没有太多 redux 的内容在里面,但他是中间件模式的核心函数,把它视为一个🔧工具函数也是可以的
比如借用这个思路来结合 HOC 来实现许多复杂的操作,不局限于此时此地,
- 将传入的 函数 存储到数组 funcs 中
- 如果传入的 函数 实际个数是1个或者干脆没有,就直接返回,进行处理
- 利用 reduce 函数遍历一边 funcs 数组,每次将前一个函数的运行结果返回到后面一个函数的参数中
- 大体过程可以简单描述一下:
- 假设有三个增强函数 funcs: [one, two, three]
- 然后实际调用的效果为 three(two(one(args)))
- one(args) 返回 oneResult
- two(oneResult) 返回 twoResult
- three(twoResult) 返回 finalResult
combineReducers
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// ...数据过滤及相关报错,最后 finalReducers 为有效的 reducers 集合
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
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 = {}
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)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
combineReducers 会在对传入的 reducers 进行一系列合法校验过滤后,返回一个大的 reducer 以供使用
- 如果在非生产环境发现传入的 reducers 有为空的则抛出错误。
- 将不为空的 reducers 整合到新的集合 finalReducers。
- 如果在非生产环境会对传入的 reducers 尝试性初始化一次,如果发现初始化后返回的 state 是 undefined 则保存错误,在下面第一次调用 combination(返回的 reducer 函数) 时抛出。
- 返回一个大的 reducer(combination)。
- 当调用了 combination,一般就是 dispatch,createStore 的时候,在非生产环境会先进行合法性校验,如果发现有不合法的地方,抛出错误。
- 然后把传入的 action 传入到每一个子 reducer 里运行,得到新的 state,然后将每个子 state tree 整合起来返回。
bindActionCreators
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
// ...参数校验
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
一个提供给使用者的工具函数,用于将 dispatch 和 actionCreate 绑定,返回新的函数以供使用
- actionCreators 一个 action creator,或者一个 value 是 action creator 的对象。
- dispatch 一个由 Store 实例提供的 dispatch 函数
- 如果 actionCreators 是函数,则说明传进来的只有一个 creator,直接返回将两者绑定的函数
- 如果传进来的 actionCreators 不是函数,也不是对象,或者干脆为空则直接抛出错误
- 既然传进来的是一个 value 是 action creator 的对象,那就遍历一边,把里面每个 creator 覆盖为新的绑定过的 creator
actionTypes
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()}`
}
export default ActionTypes
生成三个 Redux 库自身使用的 actionType
- 前两个 actionType 是用于初始化用的,第三个是一次性使用的 actionType,每次调用都是独一无二的 action
- 之所以加上随机码,是为了防止和使用者自己定义的 actionType 一致而发生冲突。
isPlainObject
export default function isPlainObject(obj) {
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
}
目的是判断一个变量是否为普通对象
redux 中的普通对象是指用 { }、new Object() 创建的对象。
- 初步过滤,使用
typeof
保证变量属于typeof
中的Object
,typeof
中不止普通对象是Object
,还有null
和DOM
等。 - 利用
while
循环和Object.getPrototypeOf()
方法得到Object.prototype
。 - 如果变量的原型是
Object.prototype
则说明是普通对象。
疑惑
1. 为什么不用 **Object.prototype.toString**
去直接判断变量的类型。
如下表所示:他们的判断结果
—- | Redux | Object.prototype.toString |
---|---|---|
__proto__ : null |
❌ | ✅ |
__proto__ : { __proto__ : null } |
✅ | ✅ |
__proto__ : { __proto__ : null , constructor : Object } |
✅ | ✅ |
[Symbol.toStringTag] : '' |
✅ | ❌ |
Object.freeze({[Symbol.toStringTag] : '' }) |
✅ | ❌ |
__proto__ : null , [Symbol.toStringTag] : '' |
❌ | ❌ |
Object.freeze({__proto__ : null , [Symbol.toStringTag] : '' }) |
❌ | ❌ |
new class{} |
❌ | ✅ |
2. 为什么不直接拿 **Obejct.prototype**
去和原型比较,而要去原型链取 **Object.prototype**
。
不同执行环境下的原型会不一致,不同执行环境是指如“同域 iframe”、“node 的 vm”、“stage2 的 Realm”等情况。在这种情况下的 Object.prototype
是不一致的,自然是怎么比较都比较都是 false
的,所以我们需要往变量的原型链上找到 Object.prototype
。
warning
简单的抛出错误的工具函数,抛出两个类型的错误
console.error(error)
throw new Error(error)