Redux源码阅读笔记
在 Github 上查看 10月 14, 2023
redux
的源码相对来说比较简单,因为它仅仅是一个js
库,不包含框架相关的知识,在去年其实就看过源码了,但是最近回想起来感觉都忘了,所以写一篇笔记记录
这里的redux
版本是最新的v4.0.5
原理简析
先简单叙述一下部分的实现原理
createStore
createStore(reducer, preloadedState, enhancer)
调用createStore
有2种情况,传入了增强器enhancer
时,会将createStore
传入enhancer
,也就是在enhancer
里创建了store
后,把store
的方法增强后再返回,代码来看就是
enhancer(createStore)(reducer, preloadedState)
另一种就是忽略掉了enhancer
,直接创建store
在创建完store
后,内部会dispatch
一个type: ActionTypes.INIT
的action
,因为我们的reducer
不会有这个type
的,所以会返回初始值,这一步就是给整个state tree
赋初始值了
通常,我们都不会只有一个reducer
,所以需要使用redux
提供的合并reducer
的函数combineReducers
combineReducers
combineReducers
的原理就是依次调用传入对象的值的reducer
函数
combineReducers(reducers)
简单来理解伪代码可以是这样
// 传入
const reducers = {
count: state => state,
string: state => state
};
// 函数里处理
const keys = ["count", "string"];
// 新state
const state = {};
for (const key of keys) {
// 通过上一次key对应的state,调用对应的reducer函数,得到新的state
state[key]=reducers[key](prevKeyState)
}
return state;
applyMiddleware
applyMiddleware
是redux
自带的增强器,主要是用来增强dispatch
函数的功能,也就是提供了dispatch
函数的中间件
之前有讲,如果传入了enhancer
,会将createStore
交给增强器来办,比如使用的applyMiddleware
,流程大概就是这样
// createStore将自己交给了增强器
applyMiddleware(增强器A,增强器B)(createStore)(reducers,preloadedState)
// 函数声明大概就是这样
function applyMiddleware(增强器A,增强器B) {
return function (createStore) {
return function (reducers,preloadedState) {
const state = createStore(reducers,preloadedState);
// 取出dispatch 使用接收的增强器对他进行增强
...
}
}
}
接下来就直接铺上源码
源码分析
以下tsc
转出的JavaScript
版,并且我人为的省略掉了一些类型判断和抛出错误
本文详细代码见我的github仓库
createStore
import $$observable from "./utils/symbol-observable";
import ActionTypes from "./utils/actionTypes";
export default function createStore(reducer, preloadedState, enhancer) {
// 如果有增强器
if (typeof enhancer !== "undefined"){
return enhancer(createStore)(reducer, preloadedState);
}
// 当前的reducer
let currentReducer = reducer;
// 当前的state
let currentState = preloadedState;
// 当前的listeners
let currentListeners = [];
// 下一次的listeners
let nextListeners = currentListeners;
// 标示是否正在进行dispatch
let isDispatching = false;
// 这是一个对currentListeners的浅复制,所以我们可以将nextListeners当作一个临时的list在dispatch的过程中使用
// 这样做的目的是可以防止在dispatch调用过程中,调用subscribe/unsubscribe产生错误
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 浅复制
nextListeners = currentListeners.slice();
}
}
// 用来获取state tree
function getState() {
return currentState;
}
// 添加一个state change的监听,它会在每次dispatch调用结束后并且一部分state tree可能被改变时调用
// 你可以在这个callback里调用getState()来获取当前的state tree
// 返回值是一个函数,用来退订
function subscribe(listener) {
// 标志已经被订阅
let isSubscribed = true;
// 浅复制一次listeners
// 也就是currentListeners复制到nextListeners
ensureCanMutateNextListeners();
// 添加进nextListeners
nextListeners.push(listener);
// 返回的退订函数
return function unsubscribe() {
// 如果已经退订了,就return
// 防止多次调用函数
if (!isSubscribed) {
return;
}
// 已经退订
isSubscribed = false;
// 浅复制一次
ensureCanMutateNextListeners();
const index = nextListeners.indexOf(listener);
// 删除掉订阅的函数
nextListeners.splice(index, 1);
// currentListeners设置为null的原因是防止内存泄露
// 见https://github.com/reduxjs/redux/issues/3474
currentListeners = null;
};
}
// dispatch一个action,这是触发state改变的唯一方式
// 它只实现了基础的字面量对象action操作,如果你想要dispatch一个Promise、Observable、thunk获取其他的,你需要将创建store的函数放进响应的中间件,比如redux-thunk包
// 为了方便返回值为相同的action对象
// 如果你使用来自定义的中间件,可能会返回其他的东西,比如Promise
function dispatch(action) {
try {
isDispatching = true;
// 通过reducer获取下一个state
currentState = currentReducer(currentState, action);
}
finally {
isDispatching = false;
}
// 通知所有listeners
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
return action;
}
// 替换store当前使用的reducer来计算state
// 如果你的app实现了代码分割,并且你想动态的加载某些reducers,或者实现来redux的热重载,就需要这个方法
function replaceReducer(nextReducer) {
// ...
}
// 提供给observable/reactive库的接口
function observable() {
// ...
}
// 当store创建好了,会派发一个INIT的action,这样所有的reducer都会返回它们的初始值
// 有效填充了初始的state tree
dispatch({ type: ActionTypes.INIT });
const store = {
dispatch: dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
};
// 返回store
return store;
}
combineReducers
combineReducers
的作用就是将多个reducer
合并为一个
import ActionTypes from "./utils/actionTypes";
export default function combineReducers(reducers) {
// 获取传入的reducers对象的keys
const reducerKeys = Object.keys(reducers);
// 实际使用的reducers对象
const finalReducers = {};
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
finalReducers[key] = reducers[key];
}
// 获取reducers的key
const finalReducerKeys = Object.keys(finalReducers);
// 返回的合并为一个的reducer函数
return function combination(state = {}, action) {
// 标示state有没有改变
let hasChanged = false;
// 经过reducer处理的下一次state
const nextState = {};
// 循环调用每一个reducer函数
for (let i = 0; i < finalReducerKeys.length; i++) {
// 当前reducer的key
const key = finalReducerKeys[i];
// 当前reducer的函数
const reducer = finalReducers[key];
// 当前key对应的state的值
const previousStateForKey = state[key];
// 经过reducer函数后的下一此state值
const nextStateForKey = reducer(previousStateForKey, action);
// 当前key的值赋值给state对象
nextState[key] = nextStateForKey;
// 如果当前key的state和上一次的state不同,说明state就已经改变了
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
// 如果replace了reducers,可能会需要判断key的length
// 见https://github.com/reduxjs/redux/issues/3488
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length;
return hasChanged ? nextState : state;
};
}
bindActionCreators
bindActionCreators
的作用是简化操作,可以把dispatch
包装进我们的action creator
函数
// 绑定
function bindActionCreator(actionCreator, dispatch) {
return function (...args) {
return dispatch(actionCreator.apply(this, args));
};
}
export default function bindActionCreators(actionCreators, dispatch) {
// 只有一个函数的情况
if (typeof actionCreators === "function") {
return bindActionCreator(actionCreators, dispatch);
}
const boundActionCreators = {};
// 循环绑定
for (const key in actionCreators) {
const actionCreator = actionCreators[key];
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
return boundActionCreators;
}
compose
compose
函数是中间件applyMiddleware
的核心功能,能将多个单参数函数从右到左嵌套调用
它的调用形式如下
const a = a => a + "A";
const b = b => b + "B";
const c = c => c + "C";
const composed = compose(a,b,c);
composed("args"); // => argsCBA
源码如下
export default function compose(...funcs) {
// 参数为0个
if (funcs.length === 0) {
return (arg) => arg;
}
// 参数为1个
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
applyMiddleware
applyMiddleware
是redux
自带的增强器,用来增强dispatch功能
import compose from "./compose";
/**
* applyMiddleware函数接收middleware为参数
* 返回另一个函数,这个函数需要接收createStore为函数,这个处理是在createStore中进行的
*
* 这里是使用接收的createStore函数,把store创建出来
* 然后把dispatch和getStore传给中间件函数
* 使用compose把已经有dispatch和getStore方法当中间件组合后,将dispatch传入,得到一个新的dispatch
* 新的dispatch是经过了中间件的dispatch
*/
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, ...args) => {
const store = createStore(reducer, ...args);
// 这里做一个错误处理
// 如果在绑定中间件的时候调用dispatch会报错
let dispatch = () => {
throw new Error("...");
};
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
};
// 将dispatch和getStore方法传入中间件,得到新的数组
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// 将新的数组用compose绑定起来,再把store.dispatch传入,得到新的dispatch
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
};
};
}
阅读完源码后,我最大的感慨还是redux
的闭包运用,基本处处都是闭包
其中觉得最精妙的部分是applyMiddleware
,感觉好像很多地方都能用上这个概念