April 27, 2019
前段时间我读了 redux 的源码,redux 的源码很简洁,但质量很高,非常值得一读。如果你已经了解了 redux 的基本使用,想深入了解一下的话,可以看一下本文帮助理解。
redux 核心 API 很少,只有 5 个 exports 和一个 store API:
store 是 Redux 最核心的概念,按照 Redux 的设计理念,一个应用应该只存在一个 store.
store 存储了整个应用的状态树,dispatch action 是修改 store 的唯一方式, 通过 createStore 这个函数来创建 store.
核心代码如下:
export default function createStore(reducer) {
// 使用一个变量存储状态树
let state;
// 使用一个数组来存储当前的监听器, 监听器是一个函数
let listeners = [];
// 获取 store 中存储的状态树
const getState = () => state;
// dispatch 是修改 store 的唯一方式,
const dispatch = (action) => {
// 调用 reducer 生成新的状态树
state = reducer(state, action);
// "依次"执行listeners 数组中存储的监听器
listeners.forEach(listener => listener());
};
// 设置监听函数, state 变化时执行
const subscribe = (listener) => {
// 每设置一个监听函数, 就向 listeners 数组中添加一个监听器
listeners.push(listener);
return () => {
// subscribe 函数返回一个 unsubscribe 方法, 用于卸载监听函数, 即从 listeners 数组中移除
listeners = listeners.filter(l => l !== listener);
}
};
// 使用 createStore 创建 store 时, 调用 dispatch 来初始化 state & listeners
dispatch({});
return { dispatch, subscribe, getState };
}
当应用复杂起来时,状态树会非常庞大,combineReducers 函数用于 Reducer 的拆分,提高代码可读性
核心代码如下:
// 传入的 reducers 是个对象,包含若干 reducer
const combineReducers = reducers => {
// combineReducers 最终返回一个总的 reducer
return (state = {}, action) => {
// 从传入的 reducers 对象中取出 key 组成数组迭代
return Object.keys(reducers).reduce(
(nextState, key) => {
// 执行每个 reducers 以组成总的 reducer
nextState[key] = reducers[key](state[key], action);
return nextState;
}, // 第一个函数是一个回调函数,函数执行在数组中每个值
{} // 第二个参数为初始值
);
};
};
中间件就是一个函数,参数是一个函数数组,对 store.dispatch 方法进行了改造,发出 Action 后,执行中间件,中间件处理完毕后,执行 Reducer 改变状态树。
核心代码如下:
export default function applyMiddleware(...middlewares) {
// 第一次调用 applyMiddleware 返回的是一个函数,返回的函数再返回一个增强的 createStore 函数
// 如何实现增强的?见下一个代码片段
return createStore => (...args) => {
const store = createStore(...args);
// 中间件内部可以调用 getState 与 dispatch
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 中间件组成一个数组(chain)
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// 该数组嵌套执行 (使用了 compose 后面会讲),用于增强 dispatch
dispatch = compose(...chain)(store.dispatch);
// 返回 store 的 api 和增强的 dispatch
return { ...store, dispatch };
}
}
applyMiddleware 即 enhancer,createStore 函数会首先判断是否有第三个参数 enhancer(即 applyMiddleware),如果有的话调用 enhancer 来递归自己(前两个参数不变),实现增强自己的效果。
export default function createStore(reducer, preloadedState, enhancer) {
// 健壮性判断
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
// enhancer 执行后返回增强后的 createStore,再次执行传入前两个参数不变
return enhancer(createStore)(reducer, preloadedState);
}
}
使用 compose 来实现函数数组的嵌套执行,嵌套的函数会依次执行,这样做的好处是:方便编写插件。
核心代码只是一个 reduce 函数:
// compose 接收一个函数数组 返回嵌套执行的结果
export default function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
虽然只有一行,但是这行代码很重要,funcs.reduce 传入的函数返回的也是个函数,返回的函数将迭代的两个数组项组合成一个新的函数,这么说比较抽象,举个例子:
compose(functionA, functionB, functionC) 最终会返回 functionA( functionB( functionC(…args) ) ), 即执行顺序为 C > B > A . args 即 middlewareAPI
这是一个不常用的方法,将若干 actionCreator 转换成拥有同名 key 值的对象返回,返回的对象的每个属性都是一个函数,调用这些函数即可触发 dispatch
核心代码如下:
// 工具方法
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments));
};
}
export default function bindActionCreators(actionCreators, dispatch) {
// 健壮性处理: actionCreators 只传入一个函数
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
// 绑定后的 actions 对象
const boundActionCreators = {};
for (const key in actionCreators) {
const actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
// 每个属性都是一个绑定了 action 的函数, 直接调用相当于触发 dispatch
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}
Redux 的代码,给人一种优雅的感觉,个人感觉它出色之处: