手写一个redux和中间件

968 阅读5分钟

前言

在react实践中,对于状态管理问题是十分迫切的,由此衍生了各种状态管理库如redux,react-redux,mobx,等。 其中redux更是其中佼佼者,虽然因为使用时重复模板太多,实际开发需要额外中间件进行流程管理被大多数人诟病,但是依然不妨碍它对于状态管理设计思想的影响。以下我们便实现一个redux和redux所需中间件来更加深入的理解状态管理。

redux

本文不是针对初学者,而是对redux使用有一定了解程度的同学,帮助大家更多深入理解redux,所以一些基础概念此处不会讲解,如有需要请前往redux官网查看。

createStore

createStore是创建一个数据存储区域的核心实现,它接收两个参数,reducer,enhancer(应用中间件)。

  1. reducer定义:reducer是一个纯函数,接收旧的state和action 返回新的state
  2. reducer中不要修改传入参数,不要执行有副作用的操作,如API请求路由跳转等。
  3. 因为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代码链接