下边章节中将详细分析源码,源码分析中对于一些边界的判断、类型判断等不做重点分析,主要将分析的重点放在主流程方向上。
createStore 作为 Redux 的核心 api 之一,其作用是通过 reducer 和 中间件 middleware 构造一个为 store 的数据结构。关于 createStore 的使用可参考上一章节 Redux源码分析(1) - Redux介绍及使用 和 官方文档 createStore 。
createStore 的源码结构图如下图所示。根据是否传入enhancer,分别做不同的逻辑判断
当存在enhancer的时候,实际执行语句如下:
//createStore.js
return enhancer(createStore)(reducer, preloadedState); //记为语句(1)
在上一章节中,我们介绍过 Redux 的如何使用:
//demo
let store = createStore(rootReducer, applyMiddleware(Logger, Test)); //记为语句(2)
由语句(2)可知:enhancer 其实就是 applyMiddleware 作用中间件(此处为Logger, Test)的结果。查看 applyMiddleware 源码,大致结构如下:
//applyMiddleware.js
function applyMiddleware(...middlewares){
//createStore中对于的enhancer
return createStore => (...args) => {
//...
return {
...store,
dispatch // 覆盖store中的dispatch
};
}
}
由 applyMiddleware 的源码可知。语句(1)执行的就是 applyMiddleware 返回高阶函数的完整执行,最终返回的结果是包含 store 所有属性 和 dispatch属性的一个对象,这也与没有enhancer时,createStore输出的结果保持之一。因为 store 对象中本身就存在 dispatch 属性,由此可知 :
applyMiddleware 作用中间件的结果就是更改 store 对象的dispatch。这是前文提到过的,applyMiddleware 本质是对 dispatch 的增强。至于是如何增强的,在下文会详细分析。
首先看下此时对应的返回结果
//createStore.js
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
};
let currentReducer = reducer; // 临时reducer
let currentState = preloadedState; //当前的state值,默认为初始化preloadedState
let currentListeners = []; // 监听队列,用于存放监听事件, 发布-订阅模式
let nextListeners = currentListeners; // 浅拷贝下这个队列
let isDispatching = false; // 标志位,用来判断是否有存在正在执行的dispatch
各个变量的作用,注释中已经详细注明,不再赘述。
根据 Redux api 可知:getState 方法返回当前的 state 树。currentState 默认值 为 preloadedState,具体的
currentState 取决于 dispatch(action) 时 reducer 执行后返回的结果。其中如果 isDispatching 为 true 时,表示有 dispatch 正在执行,此时获取 state 的值会导致获取不到正确的 state。
function getState() {
if (isDispatching) {
.....
}
return currentState;
}
先来看看subscriber的使用,其中 listener 表示监听触发时,需要做的一些操作。
let unsubscribe = store.subscribe(listener)
unsubscribe()
subscribe 的源码如下:
function subscribe(listener) {
// 类型判断
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.');
}
// 同理不可以dispatch中
if (isDispatching) {
//……
}
// 用来表示订阅标记,用于避免取消订阅后再次取消
let isSubscribed = true;
// ensureCanMutateNextListeners干啥的,点击去看一下
ensureCanMutateNextListeners();
// 将 listener 存在在 发布-订阅模式的监听队列 nextListeners 中
nextListeners.push(listener);
// 返回取消的function(unsubscribe)
return function unsubscribe() {
// 如果已经取消订阅 直接直接return
if (!isSubscribed) {
return;
}
// 同理
if (isDispatching) {
//……
}
// 这里标记为 已经取消订阅
isSubscribed = false;
// 保存订阅快照
ensureCanMutateNextListeners();
// 根据索引 在监听队列里删除监听
const index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
通过源码的解读可知。listener 必须传入一个 function 类型,否则就会报错。这里用到了发布-订阅模式,执行 subscribe 方法时,将传入的 listener 存放在 监听队列 nextListeners 里,currentListeners 和 nextListeners 都是引用类型,都是指向的一个内存地址,可以理解为是一个东西。
返回值是一个 unsubscribe 函数。执行该函数,就能够取消订阅。具体来看首先判断 isSubscribed 是否为 false,如果是则代表已经取消了该订阅,再次执行改订阅则直接忽视。如果 isSubscribed 为 ture 则表示该订阅还存在,则通过 indexOf 方法找到索引后,通过 splice 方法,将该订阅从订阅队列中取消,同时不要忘记将 isSubscribed 设置为已经 false (表示已经取消)。
dispatch的使用方式如下。分发 action是触发 state 变化的惟一途径。匹配到对应的 reducer 执行之后,会返回一个新的 state。
store.dispatch(action)
dispatch 的源码如下:
function dispatch(action) {
// acticon必须是由Object构造的函数, 否则throw Error
if (!isPlainObject(action)) {
// 抛出错误
}
// 判断action, 不存在type throw Error
if (typeof action.type === 'undefined') {
// 抛出错误 'Have you misspelled a constant?'
);
}
// dispatch中不可以有进行的dispatch
if (isDispatching) {
// 抛出错误
}
try {
// 执行时标记为true
isDispatching = true;
// reducer形式如下:(state,action)=>{} , reducer本身就是个函数,由此可见dispatch(action), 就是执行reducer方法,并将currentState,action作为参数
currentState = currentReducer(currentState, action);
} finally {
// 最终执行, isDispatching标记为false, 即完成状态·
isDispatching = false;
}
// 循环监听队列,并执行每一个监听事件
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
// 执行每一个监听函数
listener();
}
// 返回传入的action
return action;
}
通过源码的解读可知。action 必须是一个纯粹的对象且必须包含type属性,否则就出抛出异常。dispatch 方法干了两件事情:
currentReducer(currentState, action);
如果 reducer 不是组合生成,以 reducer/todos 为例。则 currentReducer 就是如下:
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO': //……
case 'TOGGLE_TODO': //……
default:
return state
}
}
如果 reducer 组合生成,也即本例中 rootReducer (通过 combineReducers 组合)。查看 combineReducers 源码可知, combineReducers 组合各个 reducer 后返回的是一个名为 combination 的高阶函数, 也就是currentReducer ,有如下形式:
function combination(state = {}, action) {
// ……
return state
}
二者具有相同的形式
(state , action ) =>{ // 更改 state的逻辑}
通过上边的伪代码分析可以,不管是否通过 combineReducers 组合生成, currentReducer 都具有相同的形式, 本身作为一个函数接受参数 state 和 action ,并根据action,改变state的值。因此可以认为,dispatch(action) 时,本质就是 reducer 方法的执行,并将当前的 stare 值 currentState 和 action 作为参数,并返回一个新的 state。
在 createStore 方法中,还要其他一些我们不常用的 api
这是一个高级 API。只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到observable:
dispatch({
type: ActionTypes.INIT });
const ActionTypes = {
INIT: `@@redux/INIT${
randomString()}`,
//……
}
由上边的代码可知,初始化 reducer 时是通过 dispatch 一个随机产生的 action 。根据 reducer 的特性可知,当前初始化的 dispatch 会执行对应的 default 分支,也即会输出 reducer 中默认的 state 的值。
与 createStore 参数 preloadedState 的对比:在 createStore 方法的定义中可以接受一个preloadedState 参数,该参数会默认为当前的 state。
let currentState = preloadedState; //当前的state值,默认为初始化preloadedState
通过 dispatch 的流程可知,初始化dispatch时,preloadedState 会作为 reducer 方法执行的参数传入。当 preloadedState 不存在时,此时 reducer 的入参为 undefined。通常做法如下, 通过 es6 的默认参数给state 复制初始值,则能起到 preloadedState 的效果。猜测 Redux 这些写应该是为了兼容 es5 的默认值处理吧。
const todos = (state = [], action) => {//.......}
但是不管 preloadedState 指定与否,初始化dispatch 执行后,currentState 的值即为default 分支对应的值。由此可知,我们定义的 reducer 都要包含 default 分支,否则初始化后 state 的值就会出现异常。
Redux源码分析(1) - Redux介绍及使用
redux源码分析(2) - createStore
Redux源码分析(3) - applyMiddleware
Redux源码分析(4) - combineReducers和 bindActionCreators