Description
最近闲来无事,对 Redux 的设计思想和工作原理比较感兴趣,由于自身对于 Redux 的了解称不上多透彻,所以决定阅读源码加深理解。关于 Redux 的其它我就不详细介绍了,Redux的核心概念主要就是三点:
- Store 全局单一数据源,只读
- Action 动作,对变化的描述
- Reducer 纯函数,负责对变化进行分发和处理,返回最新的数据给 Store
在 Redux 的整个工作流程中,数据流是严格单向的。修改数据的唯一途径就是派发 Action,这种严格单向数据保证了在大型多人协作项目中数据的可控性和可预测性。
我们的目标主要是 createStore.js 和 applyMiddleWare.js 文件。createStore.js 定义了 createStore 方法,也就是我们在使用 Redux 时候最先调用的方法,是 Redux 最核心的API。另外 applyMiddleWare.js 是中间件模块,这个模块比较独立。
其它的例如 bindActionCreators,combineReducers,compose 都是用于定义工具性质的方法,具体情况如果有使用时再介绍。
createStore 源码
function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
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() {
return currentState;
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
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-reference/store#subscribe(listener) for more details.'
)
}
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);
};
}
function dispatch(action) {
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.')
}
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 });
return store;
}
dispatch({ type: ActionTypes.INIT });
function observable() {
// observable 方法的实现
}
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
我们一步一步来解析该方法的内部逻辑
1. 调用 createStore 方法传入三个参数 reducer,初始状态内容,中间件
function createStore(reducer, preloadedState, enhancer)
2. 前置校验
- 处理没有传入初始状态内容的时候的情况
- 若 enhancer 不为空,则用 enhancer 包装 createStore
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// 此时第二个参数会被认为是 enhancer(中间件)
enhancer = preloadedState;
preloadedState = undefined;
}
// 当 enhancer 不为空时,便会将原来的 createStore 作为参数传入到 enhancer 中
if (typeof enhancer !== 'undefined') {
return enhancer(createStore)(reducer, preloadedState);
}
3. 定义内部变量
// 记录当前的 reducer,因为 replaceReducer 会修改 reducer 的内容
let currentReducer = reducer;
// 记录当前的 state
let currentState = preloadedState;
// 声明 listeners 数组,这个数组用于记录在 subscribe 中订阅的事件
let currentListeners = [];
// nextListeners 是 currentListeners 的快照
let nextListeners = currentListeners;
// 该变量用于记录当前是否正在进行 dispatch
let isDispatching = false
4. 定义 ensureCanMutateNextListeners 方法,用于确保 currentListeners 和 nextListeners 不会指向同一个引用,为何需要 nextListeners 是 currentListeners 的副本,我们后续再解释。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
5. 定义 getState 方法,该方法用于获取当前的状态
function getState() {
return currentState;
}
6. 定义 subscribe 方法,用于订阅监听函数
- 前置校验
// 校验 listener 的类型
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 禁止在 reducer 中调用 subscribe
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-reference/store#subscribe(listener) for more details.'
)
}
- 调用 ensureCanMutateNextListeners 函数
// 确保 nextListeners 与 currentListeners 不指向同一个引用
ensureCanMutateNextListeners();
- 收集监听函数,将入参的 listener 保存到 nextListeners 数组中
nextListeners.push(listener);
- 返回一个取消订阅当前 listener 的方法
// 该变量用于防止调用多次 unsubscribe 函数
let isSubscribed = true;
return function unsubscribe() {
if (!isSubscribed) {
return;
}
isSubscribed = false;
ensureCanMutateNextListeners();
const index = nextListeners.indexOf(listener);
// 将当前的 listener 从 nextListeners 数组中删除
nextListeners.splice(index, 1);
};
7. 定义 dispatch 方法,用于派发Action调用Reducer,触发订阅。
- 前置校验
// 校验 action 的数据格式是否合法
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 约束 action 中必须有 type 属性作为 action 的唯一标识
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
- 上锁,主要是避免在 reducer 函数中重复调用 dispatch
// 若当前已经位于 dispatch 的流程中,则不允许再度发起 dispatch(禁止套娃)
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// 执行 reducer 前,先"上锁",标记当前已经存在 dispatch 执行流程
isDispatching = true
// 调用 reducer,计算新的 state
currentState = currentReducer(currentState, action)
} finally {
// 执行结束后,把"锁"打开,允许再次进行 dispatch
isDispatching = false
}
- 触发订阅
// currentListeners 被赋值为 nextListeners,
// 因此最终被执行的 listeners 和当前的 nextListeners 指向同一个引用
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
return action;
8. 定义 replaceReducer 方法,该方法用于替换 Reducer
function replaceReducer(nextReducer) {
currentReducer = nextReducer;
dispatch({ type: ActionTypes.REPLACE });
return store;
}
9. 执行一次 dispatch,完成状态初始化
// 初始化 state,当派发一个 type 为 ActionTypes.INIT 的 action,每个 reducer 都会返回它的初始值
dispatch({ type: ActionTypes.INIT });
10. 定义 observer 方法,在 redux 内部使用,开发者一般不会直接接触,可忽略
function observable() {
// observable 方法的实现
}
11. 将上述定义的方法放进 store 对象返回
// 将定义的方法包裹在 store 对象里返回
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
通过分析完 createStore 的整个工作逻辑,不难看出 Redux 主流程中最重要的是 dispatch 和 subscribe 方法,其中 dispatch 将之前提到的 action,reducer 和 store 串联起来,subscribe 方法则实现了 redux 特有的 ”发布-订阅”模式。
在实际的开发过程中,我们只有在需要监听状态变化时,才会调用 subscribe。
subscribe 接收一个 Function 类型的 listener 作为参数,返回的正好是这个 listener 的解绑函数。当dispatch action 发生时,Redux 会在 reducer 执行完毕后,将 listeners 数组中的监听函数逐个执行。
我们之前有提到 currentListeners 和 nextListeners 确保不会指向同一个引用,
在之前的源码解析中,我们看到 subscribe 中的订阅过程和 dispatch 中的发布过程,使用的都是 nextListeners 数组
// 订阅过程
nextListeners.push(listener);
// 触发订阅
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
正是因为所有变更都是在 nextListeners 数组中操作的,我们才需要一个 currentListeners 来确保执行过程中的稳定性。
我们知道 subscribe 返回的是一个解绑函数,如果依次订阅三个函数A,B,C,
例如
[listenerA, listenerB, listenerC]
在函数B中解绑函数A,如果不存在 currentListeners,函数A解绑之后会直接从 nextListeners 数组中消失,
[listenerB, listenerC]
此时for循环已经继续执行就会报错了。
所以为了确保这种情况不出现,Redux 内部使用 ensureCanMutateNextListeners 函数将 nextListeners 和 当前正在执行的 listeners 区分开来,以确保监听函数在执行过程中的稳定性
本来是准备一篇写完两个核心模块的源码的,后面发现太长了,所以就拆分成两块,applyMiddleware 中间件模块下一篇再总结吧。