前言
在react实践中,对于状态管理问题是十分迫切的,由此衍生了各种状态管理库如redux,react-redux,mobx,等。 其中redux更是其中佼佼者,虽然因为使用时重复模板太多,实际开发需要额外中间件进行流程管理被大多数人诟病,但是依然不妨碍它对于状态管理设计思想的影响。以下我们便实现一个redux和redux所需中间件来更加深入的理解状态管理。
redux
本文不是针对初学者,而是对redux使用有一定了解程度的同学,帮助大家更多深入理解redux,所以一些基础概念此处不会讲解,如有需要请前往redux官网查看。
createStore
createStore是创建一个数据存储区域的核心实现,它接收两个参数,reducer,enhancer(应用中间件)。
- reducer定义:reducer是一个纯函数,接收旧的state和action 返回新的state
- reducer中不要修改传入参数,不要执行有副作用的操作,如API请求路由跳转等。
- 因为reducer是一个纯函数,所以当用户想要实现异步任务(延时,网络请求),就需要中间件的支持,也就是enhancer用于对reducer的功能增强
export function createStore(reducer, enhancer) {
// 使用中间件对原store加强,然后再执行用户的reducer函数,最后返回结果。
if (enhancer) return enhancer(createStore)(reducer);
let currentState; // 初始化值
let currentListeners = []; // 监听的事件集合(当数据变更时,用户想要响应的事件)
const getState = () => currentState; // 返回当前数据值
// 更新数据
const dispatch = (action) => {
currentState = reducer(currentState, action); // 操作数据
currentListeners.forEach(listener => listener()); // 当数据变更后,通知事件订阅者。
return action;
}
// 订阅事件监听
const subscribe = (listener) => {
currentListeners.push(listener);
// 返回一个函数 用于取消订阅
return () => {
const index = currentListeners.indexOf(listener);
currentListeners.splice(index, 1);
}
}
// 初始化调用,用于赋予默认值
dispatch({ type: "INIT_DISPATCH_CALL" });
return {
getState,
dispatch,
subscribe
}
}
applyMiddleware
调用中间件的执行函数,用于批量执行中间件。核心原理就是使用函数合成调用纯函数,不修改传入参数,并传入共同参数给函数(实际的中间件)使用。
// 应用中间件函数,用于批量调用中间件依次对reducer进行加强
export function applyMiddleware(...middlewares) {
// middlewares 是接收的多个中间件内容,顺序调用依次对reducer进行增强。
// 调用时返回一个函数,用于接收store(createStore),并保存在闭包中可以引用
return function (createStore) {
// 第二层的函数 用于接收reducer。(这里是不是很像函数柯里化,多参数转换为单参数函数,并返回新函数可以继续接收单参数。没错这就是柯里化的应用之一)
return function(reducer) {
const store = createStore(reducer); // 拿到初始未加强的store中的所有返回操作(get,dispatch,subscribe)。
// 中间件需要的操作集合
const midApi = {
getState: store.getState,
dispatch: (action, ...args) => enhancerDispatch(action, ...args)
// 1. 这里不能使用store.dispath。因为是被未被加强过的
// 2. 通过闭包拿到加强后的dispath,在执行时就能触发所有中间件。
// 3. 使用示例是addAsync函数,该函数接收的 dispatch => enhancerDispatch
}
// 拿到中间件并执行,获取返回后的一个函数链条,中间件内此时可接收 midApi中的参数 拿到并保存。
const middlewareChain = middlewares.map(middleware => middleware(midApi));
// 拿到加强后的dispath,也就是经过中间件操作后的结果。这里使用了函数合成(compose)用于对多个函数进行操作,并传入共同参数(store.dispatch)。
const enhancerDispatch = compose(...middlewareChain)(store.dispatch);
// 返回store原有的操作,但将dispatch赋值为经过中间件加强后的函数。
return {
...store,
dispatch: enhancerDispatch
}
}
}
}
function compose(...funcs) {
if (funcs.length === 0) return arg => arg;
if (funcs.length === 1) return funcs[0];
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
实现中间件
在react实践中,我们经常使用中间件去对reducer进行加强,在此实现由简到繁的实现几个常用中间件,希望能开拓大家的视野。
redux-logger
用于打印状态变化前后值对比,以及是由什么命令触发。
export default function logger(midApi) {
const { getState } = midApi; // 先拿到中间件的操作 logger中间件只需要读取数据即可
// next 其实就是接受的dispath(store.dispatch)函数,用闭包保存
return function(next) {
// 这里返回的其实就是真正加强的dispatch(enhancerDispatch)函数,最后用户调用执行的就是这里
return function(action) {
console.log("中间件 logger开始执行啦!");
console.log("操作命令:", action.type);
const prevState = getState();
console.log("prevState", prevState);
const returnValue = next(action); // 执行原dipatch。
console.log("nextState", getState());
console.log("中间件 logger执行结束啦!");
return returnValue; // 返回原执行结果
}
}
}
redux-thunk
有时候用户调用dispatch传入的不是一个普通对象(plain object)而是一个函数,那么就由thunk来解决。
export default function thunk(midApi) {
const { getState, dispatch } = midApi;
return next => action => {
console.log("thunk执行了");
if (typeof action === "function") { // 如果调用dispatch传入的action 是一个函数 那么直接执行该函数即可
return action(dispatch, getState); // 将dispath(这里是midApi给中间件的)回传,用于用户真正想操作更新数据时。
}
return next(action); // 如果action不是一个函数,那么直接调用dispath(这里是store.dispath)即可
}
}
redux-promise
有时候用户调用dispatch传入的不是一个普通对象(plain object)而是一个promise对象,那么就由promise来解决。
export default function promise(midApi) {
const { dispatch } = midApi;
return next => action => {
// 如果是promise那么使用promise.then调用将来要执行的dispath,否则直接执行next(store.dispatch)即可
console.log("promise执行了")
const call = isPromise(action) ? action.then(dispatch) : next(action);
return call;
}
}
// 简易版判断是否是promise
function isPromise(fn) {
return typeof fn.then === "function";
}
结语
以上便实现了redux及redux中间件的全部过程,希望对大家理解redux状态管理有所帮助。 github代码链接