一、简单介绍
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。它是为了解决react组件间通信和组件间状态共享而提出的一种解决方案。作为React全家桶的一份子,它名声赫赫,且体小精悍。很多人虽然都知道怎么使用Redux,但是对于它的原理知之甚少,下面就让我们一起来阅读一下Redux的源码,相信阅读之后定能加深你对redux的理解。
这里稍微提一下Redux的工作流程及三大原则,以方便大家回顾一下redux如何使用:
Redux 的工作流程:
1、首先由createStore来创建一个用于保存所有状态state的存储仓库store,并由store统一管理这个state。
2、当用户发出action后,即store.dispatch(action)后,store会自动调用对应的reducer,并将当前状态state和action一起作为参数传给reducer,reducer会返回新的state,来完成state的更新。
3、可以通过subscribe在store上添加一个监听函数。每当调用dispatch方法时,就会执行所有的监听函数。
4、也可以添加中间件处理副作用。
Redux 三大基本原则:
1、单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
2、state是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
3、使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
进入正题之前,先看一下redux的目录结构:
怎么样,就这么几个文件,是不是不算多,index.js是入口文件,会简单的看一下,然后接下来我们就来分析一下createStore、combineReducers、bindActionCreators、applyMiddleware、compose的源码实现。utils文件夹里是一些工具方法,代码很少,会在文章最后贴出源码及注释。当前分析的redux版本为4.0.5,本文主要以在源码里加注释的方式来方便大家阅读。注释的很详细,相信大家一定能看懂。
二、index.js入口文件
index里就是将redux的几个api都import进来,然后通过export统一暴露出去,供大家调用,源码如下:
import createStore from "./createStore";
import combineReducers from "./combineReducers";
import bindActionCreators from "./bindActionCreators";
import applyMiddleware from "./applyMiddleware";
import compose from "./compose";
import warning from "./utils/warning";
import __DO_NOT_USE__ActionTypes from "./utils/actionTypes";
// isCrushed是一个伪函数,用于验证在非生产环境下 Redux 是否被压缩,
// 如果被压缩就会给开发者一个 warn 警告提示。
function isCrushed() {}
// 所有的函数都有一个name属性,该属性保存的是该函数名称的字符串。
// 在代码压缩过程中,函数名字和变量会被缩写,所以这里是用函数name来判断是否被压缩。
if (
process.env.NODE_ENV !== "production" &&
typeof isCrushed.name === "string" &&
isCrushed.name !== "isCrushed"
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
"This means that you are running a slower development build of Redux. " +
"You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify " +
"or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) " +
"to ensure you have the correct code for your production build."
);
}
// 在index.js最后会暴露 createStore, combineReducers, bindActionCreators, applyMiddleware,
// compose, 这几个redux最主要的API以供大家使用。
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes,
};
三、createStore的源码解读
其实redux的源码里有很多代码都是一些容错处理及边缘条件判断。如果忽略这些代码,其实源码量并不多,但是为了严谨,咱们都一起看看吧。createStore方法就是用来创建一个 store 仓库,用来存放应用中所有的 state。应用中应有且仅有一个 store。createStore主要提供了几个api,最常用的就是dispatch, subscribe, getState这三个,下面来看源码:
import $$observable from "symbol-observable";
import ActionTypes from "./utils/actionTypes";
import isPlainObject from "./utils/isPlainObject";
/**
- 创建一个保存状态树的Redux存储仓库(store)。
- 改变store中数据的唯一方法是调用' dispatch() '。
- 在你的应用里应该只有一个store,为了指定状态树的不同部分如何响应action,你可以使用“组合器”
- 将几个reducers组合成一个reducer函数。
*
- @param {Function} reducer 是一个函数,有两个参数,当前状态state和要处理的action,
- 返回下一个状态state。
*
- @param {any} [preloadedState] preloadedState初始状态对象,可以很随意指定,比如服务端渲染的
- 初始状态,但是如果使用 combineReducers 来生成 reducer,那必须保持状态对象的key和
- combineReducers中的key相对应。
*
- @param {Function} [enhancer] enhancer是store的增强器函数,可以指定为中间件,持久化等,
- 但是这个函数只能用 Redux 提供的 'applyMiddleware()' 函数来进行生成。
*
- @returns {Store} 返回一个store, 它允许你读取状态state、 dispatch分发操作和subscribe订阅更改。
*/
export default function createStore(reducer, preloadedState, enhancer) {
// 检查传入的参数类型是否正确,如果 preloadedState 和 enhancer 同时为function,则抛出错误,
// 因为preloadedState必须是object, enhancer必须为function
// 如果enhancer为function, 同时还有第四个参数为function,不支持,必须使用compose组合成一个函数
if (
(typeof preloadedState === "function" && typeof enhancer === "function") ||
(typeof enhancer === "function" && typeof arguments[3] === "function")
) {
throw new Error(
"It looks like you are passing several store enhancers to " +
"createStore(). This is not supported. Instead, compose them " +
"together to a single function."
);
}
// 如果preloadedState为function,enhancer为undefined,则交换位置,说明没有传初始状态
if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
enhancer = preloadedState;
preloadedState = undefined;
}
// 如果enhancer参数存在
if (typeof enhancer !== "undefined") {
// 如果enhancer参数不是一个function,抛出错误
if (typeof enhancer !== "function") {
throw new Error("Expected the enhancer to be a function.");
}
// 如果enhancer参数存在且是一个function,会将当前的createStore函数作为参数传入enhancer函数,
// 并且,enhancer执行之后得到一个新函数,该新函数其实就是一个增强版的createStore函数,
// 新的createStore函数会把之前的reducer和preloadeState作为参数传入并执行。
// 这个enhancer参数为redux中间件提供了入口,实际使用中这里会结合applyMiddleware使用。
// ‘applyMiddleware()’后面会讲到。
return enhancer(createStore)(reducer, preloadedState);
}
// 检查reducer参数是不是一个function,如果不是,则抛出错误
if (typeof reducer !== "function") {
throw new Error("Expected the reducer to be a function.");
}
let currentReducer = reducer; // 当前store中的reducer
let currentState = preloadedState; // 当前store中的存储的状态state,将初始状态赋给currentState
let currentListeners = []; // 当前store中放置的监听函数数组
// 注意:当我们新添加一个监听函数时,只会在下一次dispatch的时候调用。
// 这是一个很重要的设计,为的就是每次在遍历监听器的时候保证当前currentListeners监听数组不变
// 试想一下只存在currentListeners的情况,如果我在某个subscribe中再次执行subscribe
// 或者unsubscribe,这样会导致当前的currentListeners监听数组大小发生改变,从而可能导致索引出错
let nextListeners = currentListeners; // 下一次dispatch时调用的监听函数数组
let isDispatching = false; // 标志是否正在执行dispatch操作
// 这个方法主要是用来确保nextListeners和currentListeners不是同一个引用
// 如果currentListeners和nextListeners相同,就浅拷贝一份
// 这样做是为了保证在订阅的过程中订阅突然添加和减少,在redux中触发dispatch的时候并不会
// 立即体现添加和减少的订阅事件,只会在下一次触发的时候才能体现。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
// 获取state,这是个最简单的api,其实就一行代码,那就是return currentState
function getState() {
// 如果正在执行dispatch操作,不可以获取state,抛出错误
if (isDispatching) {
throw new Error(
"You may not call store.getState() while the reducer is executing. " +
"The reducer has already received the state as an argument. " +
"Pass it down from the top reducer instead of reading it from the store."
);
}
// 返回当前store中的state
return currentState;
}
/**
- 添加一个监听事件。任何时候只要dispatch派发一个action操作都会调用这个事件,
- 然后state树的某些部分就会改变。
- 你可以在subscribe订阅的回调函数listener里调用' getState() '来读取当前state树。
*
- 你可以在一个监听事件中调用' dispatch() ',但需要注意以下事项:
-
- 在每次“dispatch()”调用之前subscriptions订阅列表都会被保存一份快照(浅拷贝一份)。
- 如果你在调用监听器时subscribe or unsubscribe(订阅或取消订阅),这将不会对当前正在进行的
- ' dispatch() '产生任何影响。但是,下一个' dispatch() '调用时,无论是否嵌套,
- 都将使用订阅列表的最新快照。
-
- 监听器不应该期望看到所有state更改,因为在调用监听器之前,state可能在嵌套的“dispatch()”
- 期间多次更新。但是,在' dispatch() '启动之前注册的所有订阅列表,都能够在' dispatch() '结束
- 时调用到最新的state。
*
- @param {Function} listener 在每次dispatch调用时都会执行订阅事件的回调函数listener.
- @returns {Function} 返回一个移除订阅事件的函数
*/
function subscribe(listener) {
// 如果listener不是一个function,抛出错误
if (typeof listener !== "function") {
throw new Error("Expected the listener to be a function.");
}
// 如果正在执行dispatch操作,抛出错误
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#subscribelistener for more details."
);
}
let isSubscribed = true; // 订阅标志,标志该监听器是否被订阅了
// 确保nextListeners不是currentListeners,以保证修改的是下一轮的监听函数数组nextListeners,
// 而不是currentListeners
ensureCanMutateNextListeners();
nextListeners.push(listener); // 添加一个监听函数到下一次监听函数数组中
// 返回一个取消订阅的函数,取消订阅就是从nextListeners中删除刚刚注册的监听函数
return function unsubscribe() {
// 如果没有订阅事件,直接return
if (!isSubscribed) {
return;
}
// 如果正在执行dispatch操作,抛出错误。dispatch正在执行期间不能取消订阅
if (isDispatching) {
throw new Error(
"You may not unsubscribe from a store listener while the reducer is executing. " +
"See https://redux.js.org/api-reference/store#subscribelistener for more details."
);
}
// 将订阅标志置为false
isSubscribed = false;
ensureCanMutateNextListeners(); // 确保nextListeners不是currentListeners
const index = nextListeners.indexOf(listener); // 找到监听函数索引
// 从下一轮的监听函数数组(用于下一次dispatch)中删除这个监听器。
nextListeners.splice(index, 1);
currentListeners = null; // 当前监听函数数组置为null
};
}
/**
- 改变state的唯一途径是dispatch一个action.
- reducer函数被用于创建stote,reducer将使用当前state和给定的'action'作为参数调用。
- 它的返回值将被作为state树的next状态,并且会通知监听函数state已经改变。
*
- 最基本的用法是仅支持dispatch一个普通对象的action。如果你希望dispatch一个Promise、
- 一个Observable、一个thunk或其他东西,你需要将store的创建函数封装到相应的中间件中。
- 例如,请参阅“redux-thunk”包的文档。
- 即使是中间件最终也会使用这种方法来dispatch一个普通对象的action。
*
- @param {Object} action 是一个普通的对象,表示“什么改变了”。保持action可序列化是个好主意,
- 这样你就可以记录和重播user sessions,
- 或者使用时间旅行的“redux-devtools”。一个action必须有一个“type”属性,这个属性不能是“undefined”。通常使用字符串常量表示。
*
- @returns {Object} 为了方便,返回值就是你dispatch的action。
*
- 注意,如果你想使用特定的中间件,可封装
dispatch
返回其他东西(比如, 一个异步调用的promise)
*/
function dispatch(action) {
// 判断action是否是一个对象,不是对象,抛出错误提示
if (!isPlainObject(action)) {
// actions必须为对象,你可以使用特定中间件异步调用actions
throw new Error(
"Actions must be plain objects. " +
"Use custom middleware for async actions."
);
}
// 判断action.type是否为undefined,如果是,抛出错误提示
if (typeof action.type === "undefined") {
// actions可能有一个未定义的type属性,你可能拼错了这个常量
throw new Error(
'Actions may not have an undefined "type" property. ' +
"Have you misspelled a constant?"
);
}
// 判断是否正在dispatch,如果是,抛出错误提示
if (isDispatching) {
throw new Error("Reducers may not dispatch actions.");
}
try {
// 执行dispatch时,将dispatch标志置为true,正在派发中
isDispatching = true;
// 执行reducer,参数是当前state和dispatch的action,返回值是newState
// 这就是dispatch一个action可以改变全局state的原因
currentState = currentReducer(currentState, action);
} finally {
// 最后将dispatch标志置为false,表示本次dispatch完成了
isDispatching = false;
}
// 将所有的的监听函数赋值给 listeners
const listeners = (currentListeners = nextListeners);
// 调用监听数组中的所有监听函数
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener(); // 执行每一个监听函数
}
return action;
}
/**
- 这是一个高级API,用于替换store中当前用于计算state的reducer。
*
- 如果你的应用程序实现了代码分割,而你想动态加载一些reducers,你可能需要这个。如果你为Redux
- 实现热重载机制,你可能还需要这个。
- Redux store 暴露出一个 replaceReducer 函数,该函数使用新的 root reducer 替代当前活动的
- root reducer。
- 调用该函数将替换内部 reducer 的引用,并 dispatch 一个 action 以初始化新加入的 reducer。
- @param {Function} nextReducer 用于替换的reducer
- @returns {void}
*/
function replaceReducer(nextReducer) {
// 既然是替换reducer, 类型要保持一致,不是function抛出错误提示
if (typeof nextReducer !== "function") {
throw new Error("Expected the nextReducer to be a function.");
}
currentReducer = nextReducer; // 替换内部 reducer 的引用
// 和ActionTypes.INIT的dispath相同,dispatch一个action(type为随机数),目的是初始化state。
// 任何存在于新旧root reducer中的reducers都将接收到之前的state。这将用旧state数据填充新state。
dispatch({ type: ActionTypes.REPLACE });
}
/**
- 这也是一个高级API,官方文档也没做过多介绍,平时基本用不到,感兴趣的同学可自行研究
- observable/reactive库的互用性
- @returns {observable} observable是state变化的最小可观察值.
- 有关更多信息,请查阅proposal-observable:
- https://github.com/tc39/proposal-observable
*/
function observable() {
const outerSubscribe = subscribe;
return {
/**
* mini的可观察订阅的方法。
* @param {Object} observer 是任何对象都可以用作观察者,这个观察者应该有一个`next`方法.
* @returns {subscription} 一个带有取消订阅“unsubscribe”方法的对象,用于退订observable,
* 防止可观察对象进一步发出值。
*/
subscribe(observer) {
// 期望observer是一个对象,不是的话,抛出错误提示
if (typeof observer !== "object" || observer === null) {
throw new TypeError("Expected the observer to be an object.");
}
// 获取观察者的状态
function observeState() {
// 如果有next方法,把state回调
if (observer.next) {
observer.next(getState());
}
}
observeState();
const unsubscribe = outerSubscribe(observeState);
// 返回取消订阅的方法
return { unsubscribe };
},
[$$observable]() {
return this; // 返回的this猜测应该是store
},
};
}
// 当创建一个store的时候,自己先dispatch了一下,dispatch派发一个初始的
// action(ActionTypes.INIT是一个随机的type类型),目的是为了初始化state树
dispatch({ type: ActionTypes.INIT });
// 这就是stote, 把store的几个api暴露出去
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
};
}
createStore方法的源码是最多的,相信看完createStore的源码后,你对redux的大致实现已经有了一定的了解。可以看出,createStore方法创建了一个store,但是并没有直接将这个store的状态state返回(state被存在了闭包中),而是返回了一系列方法,外部可以通过这些方法获取state(getState),或者间接地改变state(调用dispatch),再或者订阅一个监听函数(调用subscribe)来监听state的改变。
四、combineReducers的源码解读
在实际使用中,随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分。combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。接下来看源码,建议先从export default function combineReducers(reducers){}开始看:
import ActionTypes from "./utils/actionTypes";
import warning from "./utils/warning";
import isPlainObject from "./utils/isPlainObject";
// 主要用来获取各种不符合规范的错误信息
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type;
const actionDescription =
(actionType && `action "${String(actionType)}"`) || "an action";
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
);
}
// 主要用来获取各种不符合规范的警告信息
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
// 这里的reducers已经是最终的符合规范的finalReducers
const reducerKeys = Object.keys(reducers);
const argumentName =
action && action.type === ActionTypes.INIT
? "preloadedState argument passed to createStore"
: "previous state received by the reducer";
// 如果finalReducers为空,返回警告信息
if (reducerKeys.length === 0) {
return (
"Store does not have a valid reducer. Make sure the argument passed " +
"to combineReducers is an object whose values are reducers."
);
}
// 如果传入的state不是对象,返回警告信息
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
);
}
// 将传入的state与finalReducers下的key做比较,过滤出无效的key
const unexpectedKeys = Object.keys(inputState).filter(
(key) => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
);
// 缓存无效的key
unexpectedKeys.forEach((key) => {
unexpectedKeyCache[key] = true;
});
// 如果action的type是ActionTypes.REPLACE,直接return
if (action && action.type === ActionTypes.REPLACE) return;
// 如果存在无效的key,返回警告信息
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? "keys" : "key"} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
);
}
}
// 主要用来检查reducers符不符合规范,不符合规范抛出错误信息
function assertReducerShape(reducers) {
// 遍历finalReducers里所有key
Object.keys(reducers).forEach((key) => {
// 拿到key对应的reducer函数
const reducer = reducers[key];
// 给reducer函数传入一个action
const initialState = reducer(undefined, { type: ActionTypes.INIT });
// 如果得到的state为undefined,就抛出错误
if (typeof initialState === "undefined") {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
);
}
// 再次过滤,以防万一你在reducer中给ActionTypes.INIT返回了值
// 传入一个随机的action,如果返回值是undefined,就抛出错误
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION(),
}) === "undefined"
) {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
);
}
});
}
/**
- 将其值为不同的reducer函数的对象转换为一个总的reducer函数。它将调用每个子reducer,并将它们的
- 结果收集到单个state对象中,其键对应于传递的子reducer函数的键。
*
- @param {Object} reducers 是一个对象,其值对应于需要组合成一个reducer的不同的子reducer函数。
- 获取reducers的一种简便方法是使用ES6
import * as reducers
语法。 - reducers可能永远不会返回未定义的任何action。相反,如果传递给它们的state是未定义的,那么它们应该
- 返回初始状态,
- 如果传递给它们的是未识别的action,则返回当前state。
*
- @returns {Function} 返回一个合并后的reducer,并构建具有相同形状的state对象。
*/
export default function combineReducers(reducers) {
// 获取传进来的 reducers 对象的所有key组成的数组
const reducerKeys = Object.keys(reducers);
// 最终过滤后的有效的 reducers 存在这里
const finalReducers = {};
// 从reducers中过滤出有效的reducer,过虑掉不符合规范的reudcer
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
if (process.env.NODE_ENV !== "production") {
// 如果是在开发环境下
if (typeof reducers[key] === "undefined") {
// 获取每一个key对应的值(子reducer),判断值是否为undefined, 如果是抛出警告
warning(`No reducer provided for key "${key}"`);
}
}
// 判断子reducer是否为function,如果是,赋值给finalReducers
if (typeof reducers[key] === "function") {
finalReducers[key] = reducers[key];
}
}
// 过滤后的符合规范的所有reducers对象的key组成的数组
const finalReducerKeys = Object.keys(finalReducers);
// 这是用来确保在开发环境下,我们不会对同一个不符合规范的key发出多次警告。
let unexpectedKeyCache;
if (process.env.NODE_ENV !== "production") {
unexpectedKeyCache = {}; // 保存不符合规范的key的缓存用于下面做警告
}
// 这是用来检查finalReducers中的每个子reducer接受一个初始action或一个未知的action时,
// 是否依旧能够返回有效的值。
let shapeAssertionError;
try {
// 点进去看
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
// 返回合并后的reducer,也就是createstore中的reducer参数,
// 即currentReducer(currentState, action);
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; // 用于判断state是否改变
const nextState = {}; // 总的新状态
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i];
// 拿到本次循环的子reducer
const reducer = finalReducers[key];
// 拿到该子reducer对应的旧状态,state状态树下的key与finalReducers下的key相同的
const previousStateForKey = state[key];
// 将拿到的每个子reducer对应的state,与action一起作为参数传给每个子reducer执行,得到新状态
const nextStateForKey = reducer(previousStateForKey, action);
// 如果新状态等于undefined,就抛出错误
if (typeof nextStateForKey === "undefined") {
const errorMessage = getUndefinedStateErrorMessage(key, action);
throw new Error(errorMessage);
}
// 将该新状态赋值给总的新状态对象对应的key
nextState[key] = nextStateForKey;
// 判断新状态和旧状态是否相等,如果不相等说明状态改变了
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
// 如果子reducer不能处理该action,那么会返回previousStateForKey也就是旧状态,
// 当所有状态都没改变时,直接返回之前的state。
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length;
// 如果state改变,就返回新的state, 否则返回旧state
return hasChanged ? nextState : state;
};
}
combineReducers函数总结来说就是接收一个拆分之后的reducer函数组成的对象,将这个对象过滤后返回一个函数combination(其实就是合并后的reducer函数)。该函数里有一个过滤后的对象 finalReducers,遍历该对象,然后执行对象中的每一个 reducer 函数,最后将新的 state 返回。
五、bindActionCreators的源码解读
bindActionCreators是redux提供的一个辅助方法,能够让我们以方法的形式来调用action。同时,自动dispatch对应的action。它的源码很少,直接看源码:
// 这个函数的主要作用就是返回一个函数,当我们调用返回的这个函数的时候,就会自动的dispatch对应的action
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments));
};
}
/**
- 把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对
- 每个 action creator 进行包装,以便可以直接调用它们。
- 这只是一个方便的方法,你可以自己调用' store.dispatch(myactioncreator.dosomething() '。
*
- 一般情况下你可以直接在 Store 实例上调用 dispatch。如果你在 React 中使用 Redux,react-redux 会
- 提供 dispatch 函数让你直接调用它。
- 惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想
- 让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。
- 为方便起见,你也可以传入一个函数作为第一个参数,它会返回一个函数。
*
- @param {Function|Object} actionCreators 一个action creator函数,或者一个 value 是
- action creator 的对象。
*
- @param {Function} dispatch 一个由 Store 实例提供的 dispatch 函数。
*
- @returns {Function|Object} 一个与原对象类似的对象,只不过这个对象的 value 都是会直接
- dispatch 原 action creator 返回的结果的函数。
- 如果传入一个单独的函数作为 actionCreators,那么返回的结果也是一个单独的函数。
*/
export default function bindActionCreators(actionCreators, dispatch) {
// 如果actionCreators是一个函数的话,就调用bindActionCreator方法对actionCreators函数和dispatch进行绑定
if (typeof actionCreators === "function") {
return bindActionCreator(actionCreators, dispatch);
}
// actionCreators必须是函数或者对象中的一种,且不能为null,否则抛出错误
if (typeof actionCreators !== "object" || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${
actionCreators === null ? "null" : typeof actionCreators
}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
);
}
const boundActionCreators = {};
// 遍历所有actionCreators对象里面的key对应的actionCreator函数
for (const key in actionCreators) {
const actionCreator = actionCreators[key];
// 如果actionCreator是函数,就调用bindActionCreator方法对actionCreator函数和dispatch进行绑定
// 不是函数的直接过滤掉
if (typeof actionCreator === "function") {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
// 返回绑定之后的boundActionCreators对象
return boundActionCreators;
}
六、applyMiddleware的源码解读
applyMiddleware方法的源码虽然不多,但是应该是redux源码中最难理解的一部分。在介绍applyMiddleware之前先来讲一下函数柯里化,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。示例如下:
function add(x, y) { return x + y }
console.log(add(1, 2)) // 3
// 对于以上函数如果使用柯里化可以这样改造
function add2(x) {
return y => {
return x + y
}
}
console.log(add2(1)(2)) // 3
applyMiddleware返回一个柯里化的函数,其实返回的就是一个enhancer增强器函数。所以调用这个函数应该这样写 applyMiddleware(...middlewares)(createStore)(reducer, preloadedState)。
如果不使用中间件,redux工作流程是这样的:
1、dispatch派发一个标准的纯对象格式的action;
2、这个action会被对应reducer处理;
3、reducer根据action更新store中的state。
而使用了中间件后,redux工作流程是这样的:
1、dispatch派发一个"action"(这里的action不一定是标准的action);
2、这个"action"会先被中间件处理(比如在这里发送一个异步请求);
3、中间件处理结束后,再发送一个"action"(有可能是原来的action,也可能是不同的action,因中间件功能不同 而不同);
4、中间件发出的"action"可能继续被另一个中间件处理,进行类似3的步骤。即中间件可以链式串联;
5、最后一个中间件处理完后,dispatch一个符合reducer处理标准的action(纯对象格式的action);
6、这个标准的action被reducer处理,reducer根据action更新store中的state。
那么中间件该如何融合到redux中呢?
在上面的流程中,2-4的步骤是关于中间件的,但凡我们想要添加一个中间件,我们就需要写一套2-4的逻辑。
如果我们需要多个中间件,我们就需要考虑如何让他们串联起来。如果每次串联都写一份串联逻辑的话,就不够灵活,万一需要增删改或调整中间件的顺序,都需要修改中间件串联的逻辑。
所以redux提供了一种解决方案,那就是applyMiddleware,将中间件的串联操作进行了封装,经过封装后,上面的步骤2-5就可以成为一个整体。我们只需要改造store自带的dispatch方法。action发生后,先给中间件处理,最后再dispatch一个action交给reducer去改变状态。下面看源码:
import compose from "./compose";
/**
- applyMiddleware() 顾名思义,应用中间件,该方法返回的就是一个enhancer增强器。
- 本质就是在 dispatch 更改 reducer 之前做一些操作,具体的实现其实就是对 store 的增强,
- 最终是对 store 中的 dispatch 的增强。
*
- @param {...Function} middlewares 遵循 Redux middleware API 的函数。每个 middleware
- 接受 Store 的 dispatch 和 getState 函数作为命名参数,并返回一个函数。
- 该函数会被传入 被称为 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,
- 这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个
- middleware 会接受真实的 store 的dispatch 方法作为 next 参数,并借此结束调用链。
- 所以,middleware 的函数签名是 ({ getState, dispatch }) => next => action。
*
- @returns {Function} 一个应用了 middleware 后的 store enhancer。这个 store enhancer 的签名
- 是 createStore => createStore,
- 但是最简单的使用方法就是直接作为最后一个 enhancer 参数传递给 createStore() 函数。
*/
export default function applyMiddleware(...middlewares) {
// 中间件的使用方式如下:
// const store = createStore(rootReducer, applyMiddleware(middleware1, middleware2));
// 在 createStore 方法中,当存在 enhancer 时,执行语句:
// enhancer(createStore)(reducer, preloadedState);
// 可知 enhancer 即为 applyMiddleware(middleware1, middleware2) 的返回值,
// 也就是下边返回的高阶函数。
return (createStore) => (...args) => {
// 这里执行 createStore 函数。把 applyMiddleware 函数最后一次调用的参数传进来。
// createStore 参数即为 redux 的 createStore方法的参数,
// 即 ...agrs 表示 reducer, preloadedState 参数。
// store 即为createStore方法在没有enhancer的时候执行的结果。也就是中间件作用前的原始store。
const store = createStore(...args);
// 定义一个临时的dispatch方法,作用是在dispatch改造完成前调用dispatch只会打印错误信息
// 不允许在构建中间件时进行dispatch。其他中间件将不应用于此dispatch。
let dispatch = () => {
throw new Error(
"Dispatching while constructing your middleware is not allowed. " +
"Other middleware would not be applied to this dispatch."
);
};
// 接下来就是通过传入的getState方法将每个中间件与state关联起来,得到改造后的dispatch函数。
// 传递 getState、 dispatch到中间件。这也是中间件中能访问state的原因。
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
};
// middlewares是中间件函数数组,形如[(next)=>action=>{...next(action)...}],
// 其中每个中间件形式为 store => next => action =>{},中间件函数的返回值是一个
// 改造dispatch的函数。
// 调用数组中的每个中间件函数,得到所有的改造函数
const chain = middlewares.map((middleware) => middleware(middlewareAPI));
// 将这些改造函数通过compose函数组合成一个函数,用组合后的函数去改造store的dispatch
// compose方法的作用是,例如这样调用:
// compose(funcA,funcB,funcC),返回一个函数为: (...args) => funcA( funcB( funcC(...args) ) )
// 即传入的dispatch被funcC改造后得到一个新的dispatch,再将新的dispatch传递给funcB改造,以此类推。
dispatch = compose(...chain)(store.dispatch);
// 返回增强后的store,即用改造后增强的dispatch方法替换store中的dispatch
return {
...store,
dispatch,
};
};
}
七、compose的源码解读
compose是一个辅助工具函数,代码很少,直接看源码:
/**
- 通过使用 reduce 函数从右到左来组合多个单参数函数。最右边的函数可以接受多个参数,因为它为生成的
- 复合函数提供了签名。
- compose是函数式编程中的方法,为了方便,被放到了 Redux 里。当需要把多个 store 增强器
- 依次执行的时候,需要用到它。
*
- @param {...Function} funcs 需要合成的多个函数。预计每个函数都接收一个参数。它的返回值将
- 作为一个参数提供给它左边的函数,以此类推。
- 例外是最右边的参数可以接受多个参数,因为它将为由此产生的函数提供签名。
- @returns {Function} 从右到左把接收到的函数合成后的最终函数。
- 例如,compose(f, g, h) 形象为 (…args) => f (g (h (…args)))。
*/
export default function compose(...funcs) {
// 如果没有传入任何函数时,返回一个函数:arg => arg
if (funcs.length === 0) {
return (arg) => arg;
}
// 如果只传入了一个函数,直接返回这个函数
if (funcs.length === 1) {
return funcs[0];
}
// 传入多个函数时,返回组合后的函数
return funcs.reduce((a, b) => (...args) => a(b(...args)));
// reduce是Array对象的内置方法,array.reduce(callback)的作用是:
// 给array中每一个元素应用callback函数
// callback函数:callback(accumulator, value, [index], [array])
// 参数accumulator:上一次调用callback的返回值
// 参数value:当前数组元素
// 参数index:可选,当前元素的索引
// 参数array:可选,当前数组
}
八、简单说一下utils里的工具函数
1、actionTypes.js的源码:
/**
- 这些是Redux保留的私有action types。
- 对于任何未知action,必须返回当前state。
- 如果当前state是undefined,则必须返回初始state。
- 不要在代码中直接引用这些action types。
*/
// 生成一个随机字符串
const randomString = () =>
Math.random().toString(36).substring(7).split("").join(".");
const ActionTypes = {
INIT: @@redux/INIT${randomString()}
, // 初始化时action的type,没有action参数的时候用
REPLACE: @@redux/REPLACE${randomString()}
,
PROBE_UNKNOWN_ACTION: () => @@redux/PROBE_UNKNOWN_ACTION${randomString()}
,
};
export default ActionTypes;
2、isPlainObject.js的源码:
/**
- 检查是否是普通对象
- @param {any} obj 要检查的对象。
- @returns {boolean} 如果参数是一个对象,则为返回true。
*/
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;
}
3、warning.js的源码:
/**
- 如果存在,则在控制台中打印警告。
*
- @param {String} message 警告信息
- @returns {void} 没有返回值
*/
export default function warning(message) {
/ eslint-disable no-console /
if (typeof console !== "undefined" && typeof console.error === "function") {
console.error(message);
}
/ eslint-enable no-console /
try {
// 这个错误是为了方便而抛出的,因此如果在控制台中启用“break on all exceptions”,
// 它将在这一行暂停执行。
throw new Error(message);
} catch (e) {} // eslint-disable-line no-empty
}
至此,所有的源码都解读结束啦,如果哪里讲的不对,欢迎在评论区留言指正。谢谢。