最近学习了状态管理库 redux 的源码,简单记录一下学习笔记。
redux 简介
redux
是 JavaScript 的状态管理库,提供可预测的状态管理。
redux
有几个核心概念:
store
:唯一存放状态的容器state
:状态action
:状态更新的动作reducer
:action
对应的更新状态的动作函数dispatch
:通过action
找到对应的reducer
,更新state
redux
的使用有三大原则:
-
单一数据源
整个应用的
state
存储在一个状态树中,整个状态树只存在于唯一的store
里。 -
state
只读状态是只读的,不要直接修改
state
。唯一改变state
的方法就是调用dispatch
触发action
。直接修改state
的话,并不会通知所有订阅者。 -
纯函数执行修改
reducer
是纯函数,传入先前的state
和action
,返回新的state
。
redux 简单使用
如下代码所示,用redux
简单写了个计数的 demo,仅用到了createStore
这个方法。
import { createStore } from 'redux';
// 初始状态
const initialState = 0;
// 创建reducer
const reducer = (preState = initialState, action) => {
const { type } = action;
switch (type) {
case 'ADD':
return preState + 1;
case 'SUB':
return preState - 1;
default:
return preState;
}
};
// 创建store
const store = createStore(reducer);
// 订阅,当state改变,打印state
store.subscribe(() => console.log(store.getState()));
// 生成action的函数
const add = () => ({ type: 'ADD' });
const sub = () => ({ type: 'SUB' });
store.dispatch(add()); // 1
store.dispatch(add()); // 2
store.dispatch(add()); // 3
store.dispatch(add()); // 4
store.dispatch(sub()); // 3
store.dispatch(sub()); // 2
通过这个 demo,可以大致了解redux
的工作流程:
- 首先要创建
reducer
函数,它是一个纯函数,接收之前的状态preState
和action
,根据action
作一些处理之后,返回新的state
。 - 使用
createStore
方法创建store
,接收创建好的reducer
。其实还可以传入其他参数,后面阅读源码会看到。 store.subscribe
订阅消息,参数是函数。只有订阅了,在dispatch
时,才会通知每个监听者。- 定义好不同的
action
,这里定义了两个生成action
的函数,需要用时,调用这个函数即可。 - 最后就是调用
store.dispatch
,根据action
更新state
,通知监听者。
redux 源码笔记
整个redux
源码的结构如下。
utils
文件夹主要是一些轻量工具函数,比较简单。index.js
是入口文件,暴露了供开发者使用的方法。其他文件则是相应方法的实现。
下面根据不同文件,整理了自己对其的理解和笔记。
utils
(1)actionTypes.js
这些是redux
的私有action
,在代码中不要直接引用这些action
。
// 随机生成长度为6的字符串,并用.连接
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;
randomString
使用了Number.prototype.toString()
,覆盖了Object
上的toString()
方法。转换基数为36
,10 个数字和 26 个字母,表示随机生成随机字符串,包含数字和字母。截取 6 位,用.
连接。
最后向外暴露了 3 个action
:INIT
、REPLACE
、PROBE_UNKNOWN_ACTION
。
(2)formatProdErrorMessage.js
此文件用来格式化生产环境的error
。不要直接引用此模块。
function formatProdErrorMessage(code) {
return (
`Minified Redux error #${code}; visit https://redux.js.org/Errors?code=${code} for the full message or ` +
'use the non-minified dev environment for full errors. '
);
}
export default formatProdErrorMessage;
(3)isPlainObject.js
该方法用来判断一个对象是否是简单对象。
简单对象满足obj.__proto__ === Object.prototype
。new Object
和字面量创建出的对象,是简单对象。
换句话说,此方法判断的是一个对象是否是Object
的实例对象。
export default function isPlainObject(obj) {
// 因为typeof null显示为'object',所以单独讨论
if (typeof obj !== 'object' || obj === null) return false;
let proto = obj;
// 沿着__proto__向上遍历原型链
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
// 最后判断原始对象的__proto__是否等于原型链的最上端
return Object.getPrototypeOf(obj) === proto;
}
(4)kindOf.js
该方法是一个判断数据类型的函数,比较简单。值得学习之处是,typeof
、constructor
、instanceof
都可以在一定条件下判断数据类型。
function miniKindOf(val) {
if (val === void 0) return 'undefined';
if (val === null) return 'null';
// 以下数据类型不用处理
const type = typeof val;
switch (type) {
case 'boolean':
case 'string':
case 'number':
case 'symbol':
case 'function': {
return type;
}
default:
break;
}
// 单独判断数组、日期、错误对象
if (Array.isArray(val)) return 'array';
if (isDate(val)) return 'date';
if (isError(val)) return 'error';
// 通过val.constructor.name判断以下类型
const constructorName = ctorName(val);
switch (constructorName) {
case 'Symbol':
case 'Promise':
case 'WeakMap':
case 'WeakSet':
case 'Map':
case 'Set':
return constructorName;
default:
break;
}
// other
return type.slice(8, -1).toLowerCase().replace(/\s/g, '');
}
function ctorName(val) {
return typeof val.constructor === 'function' ? val.constructor.name : null;
}
function isError(val) {
return (
val instanceof Error ||
(typeof val.message === 'string' &&
val.constructor &&
typeof val.constructor.stackTraceLimit === 'number')
);
}
function isDate(val) {
if (val instanceof Date) return true;
return (
typeof val.toDateString === 'function' &&
typeof val.getDate === 'function' &&
typeof val.setDate === 'function'
);
}
export function kindOf(val) {
let typeOfVal = typeof val;
// 开发环境下,进一步处理
if (process.env.NODE_ENV !== 'production') {
typeOfVal = miniKindOf(val);
}
return typeOfVal;
}
(5)warning.js
判断了下console
是否存在,打印错误信息。
export default function warning(message) {
// 检测console是否存在
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message);
}
try {
throw new Error(message);
} catch (e) {}
}
index.js
该文件是redux
的入口文件,向开发者暴露了几个方法。
createStore
:创建store
combineReducers
:将多个reducer
合并成单个reducer
bindActionCreators
:将action
与对应的dispatch
方法绑定,生成可以直接执行action
的函数applyMiddleware
:增强redux
功能compose
:组合函数,连接多个函数
function isCrushed() {}
// 若开发环境压缩了代码,提示warning
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.'
);
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
// 不要使用自带的action
__DO_NOT_USE__ActionTypes
};
这里定义了一个isCrushed
的空函数,目的是判断用户是否压缩了代码,因为如果压缩了代码,函数isCrushed
的函数名就会被替换,不再是isCrushed
了。若在开发环境下压缩了代码,给出warning
提示。
createStore.js
此方法是redux
源码阅读的重点,也是使用redux
的第一个步骤。
该方法接收三个参数:reducer
、preloadedState
、enhancer
。reducer
是产生新state
的函数,preloadedState
代表初始状态,enhancer
是增强redux
用的函数。
向外暴露了一些方法,其中dispatch
、subscribe
、getState
是开发中常用的方法。
export default function createStore(reducer, preloadedState, enhancer) {
// 判断是否传入多个enhancer,确保只传入一个enhancer
// 若要使用多个enhancer,可以将其合并为一个函数
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. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
);
}
// 若第二个参数是函数,且没有传入第三个参数
// 则第二个参数作为enhancer,preloadedState不传入
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
// 首先若传入了enhancer,则返回增强后的createStore执行的结果
if (typeof enhancer !== 'undefined') {
// 确保enhancer是函数
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(enhancer)}'`
);
}
// 返回增强后的createStore执行的结果
return enhancer(createStore)(reducer, preloadedState);
}
// 确保reducer是函数,若不是函数,显示其类型
if (typeof reducer !== 'function') {
throw new Error(
`Expected the root reducer to be a function. Instead, received: '${kindOf(
reducer
)}'`
);
}
// 当前的reducer
let currentReducer = reducer;
// 当前的state
let currentState = preloadedState;
// 当前订阅者列表
let currentListeners = [];
let nextListeners = currentListeners;
// 锁,保证数据一致性
let isDispatching = false;
// 当nextListeners和currentListeners是同一个引用时,
// 将currentListeners的拷贝赋值给nextListeners
// 防止当前队列执行的时候,影响到自身
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
// 直接返回当前的state
// 从这里可以得到若直接修改currentState,并不会通知订阅者
function getState() {
// 确保没有其他reducer操作
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.'
);
}
return currentState;
}
// 添加订阅者
// 返回取消订阅的函数
function subscribe(listener) {
// 确保listener是个函数
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(listener)}'`
);
}
// 确保数据唯一性
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/store#subscribelistener for more details.'
);
}
// 该订阅者在订阅状态
let isSubscribed = true;
// 若nextListeners和currentListeners为同一个引用
// 将currentListeners作一次浅拷贝,给nextListeners
ensureCanMutateNextListeners();
// 新的订阅者加入订阅者列表中
nextListeners.push(listener);
// 返回取消订阅的函数
return function unsubscribe() {
// 若已经取消订阅,不继续执行
if (!isSubscribed) {
return;
}
// 保证数据唯一性
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
);
}
// 取消订阅
isSubscribed = false;
ensureCanMutateNextListeners();
// 将该订阅者从订阅者列表中删除
const index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}
// 分派action,这是触发state更新的唯一方法
// action仅支持简单对象,若action是Promise、Observable等,需要使用中间件
// action表明了做了什么改变,必须有type属性,并且非undefined,一个好的实践为type是字符串常量
// 返回dispatch的action,若使用了中间件,可能返回其他
function dispatch(action) {
// 确保action是简单对象
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(
action
)}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
);
}
// 确保action.type存在
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
);
}
// 确保当前没有在执行其他的reducer操作
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
// 加锁,防止后续的reducer操作
isDispatching = true;
// 调用当前的reducer,返回新的state,赋值给currentState
currentState = currentReducer(currentState, action);
} finally {
// 无论是否有错误,都会执行的语句
// 当前reducer执行完毕后,解锁
isDispatching = false;
}
// 通知订阅者
// currentListeners = nextListeners是为了下一次执行的时候,会重新生成一个新的拷贝
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
// 执行订阅者的函数,不传入参数
const listener = listeners[i];
// 执行函数
listener();
}
// 最后返回当前的action
return action;
}
// 替换reducer
// 使用场景:
// 1. 代码分割,立即加载reducers的时候
// 2. 实现redux热加载机制的时候
function replaceReducer(nextReducer) {
// 确保nextReducer是函数
if (typeof nextReducer !== 'function') {
throw new Error(
`Expected the nextReducer to be a function. Instead, received: '${kindOf(
nextReducer
)}`
);
}
// 替换reducer
currentReducer = nextReducer;
// 触发state更新
dispatch({ type: ActionTypes.REPLACE });
}
// 一般用不到
function observable() {
// ...
}
// 初始化state,否则第一次的currentState为undefined
dispatch({ type: ActionTypes.INIT });
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
};
}
combineReducers.js
该方法的作用是合并多个reducers
为单个reducer
。输入参数是一个拥有多个reducer
的对象,最后返回一个函数,处理所有的reducer
。
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
const reducerKeys = Object.keys(reducers);
const argumentName =
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer';
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.'
);
}
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "${kindOf(
inputState
)}". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
);
}
// 找出inputState里有的key,但reducers集合里没有的key
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
);
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true;
});
// 如果是替换reducer的action,则提前退出,不打印异常
if (action && action.type === ActionTypes.REPLACE) return;
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) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key];
const initialState = reducer(undefined, { type: ActionTypes.INIT });
// 确保初始值不为undefined
if (typeof initialState === 'undefined') {
throw new Error(
`The slice reducer for key "${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.`
);
}
// 确保遇到未知的action,返回初始值,并且不为undefined
// 确保没有占用redux的命名空间
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
throw new Error(
`The slice reducer for key "${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
// 输入参数reducers是一个对象,值是需要合并的reducer
// 会调用所有的子reducer,聚合所有结果合并为一个object
// 返回合并后的单个reducer
export default function combineReducers(reducers) {
// reducers对象的key数组
const reducerKeys = Object.keys(reducers);
// 最终要返回的reducer
const finalReducers = {};
// 遍历key数组,浅拷贝reducers
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
// 若开发环境,且当前reducer函数名不再存在,给出warning
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`);
}
}
// 当前reducer是函数,添加到finalReducers中
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
// 获取finalReducers的所有key
const finalReducerKeys = Object.keys(finalReducers);
// 确保不警告多次相同的key
let unexpectedKeyCache;
if (process.env.NODE_ENV !== 'production') {
// 开发环境为{}
unexpectedKeyCache = {};
}
let shapeAssertionError;
try {
// 确保所有reducers遇到未知的action,返回初始值,且不为undefined
// 确保没有占用redux命名空间
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
// 返回最后的reducer
return function combination(state = {}, action) {
// 错误信息
if (shapeAssertionError) {
throw shapeAssertionError;
}
// 生产环境,找出state里面没有对应reducer的key,给出提示
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
);
if (warningMessage) {
warning(warningMessage);
}
}
// 表示state是否已被更改
let hasChanged = false;
// 改变后的state
const nextState = {};
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i];
const reducer = finalReducers[key];
// 当前key的state值
const previousStateForKey = state[key];
// 执行当前reducer,拿到state
const nextStateForKey = reducer(previousStateForKey, action);
// 对新的state做undefined检验
if (typeof nextStateForKey === 'undefined') {
const actionType = action && action.type;
throw new Error(
`When called with an action of type ${
actionType ? `"${String(actionType)}"` : '(unknown type)'
}, the slice reducer for key "${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.`
);
}
// 新的state放在相应nextState中
nextState[key] = nextStateForKey;
// 判断新旧state是不是同一个引用,若state发生变化,则肯定已经改变
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
// 发生变化则返回nextState,否则返回state
return hasChanged ? nextState : state;
};
}
bindActionCreators.js
该方法的作用是将action
与对应的dispatch
方法绑定,生成可以直接执行action
的函数。
输入参数actionCreators
可以是创建action
的函数对象,也可以是单个创建action
的函数。dispatch
即为store
提供的dispatch
函数。
返回一个与原对象类似的对象,只不过这个对象的value
都是会直接dispatch
原action creator
产生的action
。如果传入一个单独的函数作为actionCreators
,那么返回的结果也是一个单独的函数。
// actionCreator是产生action的函数,执行之后,会得到一个action
// 得到的action再传递给dispatch
// bindActionCreator函数返回一个自动执行dispatch的方法
function bindActionCreator(actionCreator, dispatch) {
// 闭包
return function () {
// 返回结果为dispatch这个actionCreator并传参
return dispatch(actionCreator.apply(this, arguments));
};
}
// actionCreators:创造action的函数对象
export default function bindActionCreators(actionCreators, dispatch) {
// 如果是一个函数,直接执行bindActionCreator并返回
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
// 错误处理
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, but instead received: '${kindOf(
actionCreators
)}'. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
);
}
const boundActionCreators = {};
// 遍历每个函数
for (const key in actionCreators) {
// 拿到每个函数
const actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
// 将自动执行dispatch的方法放到boundActionCreators中
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}
compose.js
该方法将多个函数连接起来,上一个函数的返回值作为下一个函数的参数输入。
注意,它的执行顺序为从右到左。
// 将多个函数连接起来:上一个函数的返回值作为下一个参数的输入
// 最终得到最后的返回值
// 从右向左的顺序执行
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
// 利用reduce方法执行每个中间件函数,并将上一个函数的返回作为下一个函数的参数
// a:上一次调用回调的返回值,b:当前处理的元素
// 所以是从右向左的顺序执行
// compose(f, g, h)
// (...args) => f(g(h(...args)))
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args))
);
}
applyMiddleware.js
该方法用来增强redux
功能,主要执行过程为,在dispatch
的时候,按照传入的中间件顺序,依次执行,最后返回一个增强后的store.dispatch
方法。
这里用到了上面的compose
方法。
一个注意点,这里首先定义了dispatch
为一个抛出错误函数,目的是为了防止在中间件构造过程中调用dispatch
。构造完成后,再将最终的dispatch
赋值完成。
export default function applyMiddleware(...middlewares) {
// 返回一个参数为createStore的函数
return createStore =>
(...args) => {
// 创建store
const store = createStore(...args);
// 定义一个dispatch,如果在中间件构造过程中调用,则抛出错误
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)
};
// 依次调用middleware,存放在chain数组中
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// 用compose整合chain数组,并赋值给dispatch
dispatch = compose(...chain)(store.dispatch);
// 返回增强的store
return {
...store,
dispatch
};
};
}
手写简易 redux
阅读完了redux
的源码,来实现一个简易版的createStore
方法,来实现「redux 简单使用」小节的功能。
实现createStore
的功能,核心常用的方法就是subscribe
、dispatch
、getState
这三个。
其实核心思路就是定义一个监听者列表,有新订阅时,将其回调函数放入监听者列表。dispatch
中通过reducer
更新旧状态,并通知所有订阅者。
function createStore(reducer) {
// 状态
let state;
// 监听者列表
const listeners = [];
// 订阅
function subscribe(listener) {
listeners.push(listener); // 每订阅一个,就为监听器添加一个回调函数
}
// 更新 state
function dispatch(action) {
// 调用 reducer,更新 state
state = reducer(state, action);
// 通知所有订阅者
listeners.forEach(i => i());
}
// 获取 state
function getState() {
return state;
}
// 返回 store 对象
return {
subscribe,
dispatch,
getState
};
}
参考
📘📘欢迎在我的博客上访问:
https://lzxjack.top/