深入浅出 redux中间件

741 阅读6分钟

redux中间件是什么?

理解redux中间件首先我们需要理解redux是什么, ReduxJavaScript应⽤的状态容器。它保证程序⾏为⼀致性且易于测试。当业务足够复杂时,我们就需要使用redux来存储我们的多页面共同数据 redux的使用范例:

//src/store/index.js
function countReducer(state = 0, action) {
 switch (action.type) {
 case "ADD":
   return state + (action.payload ?? 1);
 default:
   return state;
 }
}
const store = createStore(countReducer);
export default store;

// pages/Reduxpage.js 页面使用
import store from "../store";
export default class ReduxPage extends Component {
  add = () => {
    //*重点 通过dispatch改变数据
    store.dispatch({type: "ADD"});
  };
  render() {
   return (
	 <div>
	 <h3>{store.getState()}</h3>
	 <button onClick={this.add}>add</button>
	 </div>
   )
  }
 }

而我们工作中常常发现单纯的通过dispatch传递带type属性的对象修改store 内的数据远远满足不了我们的需求,此时redux中间件applyMiddleware登场了 applyMiddleware的使用范例:

//src/store/index.js
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk";     // 异步解决方案
import logger from "redux-logger";   // 打印日志
import promise from "redux-promise"; // 处理promise
function countReducer(state = 0, action) {
  //...同上
}
const store = createStore(
  countReducer,
  applyMiddleware(promise, thunk, logger)
)
export default store;

此时我们发现createStore内多了第二个参数applyMiddleware函数的返回值,那么它是干嘛的呢,它返回的是什么呢,理解了它,redux中间件原理你就掌握了, 首先我们来简单说明一下它的作用:利用 compose 写法 将thunk,logger,promise 这3个中间件组合成一个柯里化版本的 dispatch 方法 ,此方法替换掉了原始的dispatch 方法 接下面我们来看页面是怎么工作的

// pages/Reduxpage.js 页面使用
import store from "../store";
export default class ReduxPage extends Component {
  add = () => {
    store.dispatch({type: "ADD"})
  }
  //模拟异步请求数据更改store
  asyAdd = () => {
    store.dispatch((dispatch, getState) => {
      setTimeout(() => {
        dispatch({type: "ADD"})
      }, 1000)
    })
  }
  //模拟Promise更改store
  promiseMinus = () => {
    store.dispatch(
      Promise.resolve({type: "ADD", payload: 3})
    )
  }
  render() {
   return (
	 <div>
	    <h3>store改变的数据:{store.getState()}</h3>
	    <button onClick={this.add}> 同步改变数据 </button>
        <button onClick={this.asyAdd}> 异步请求数据 </button>
        <button onClick={this.promiseMinus}> promise返回数据 </button>
	 </div>
   )
  }
 }

在这里插入图片描述 可见applyMiddleware的作用就是将dispatch内传递的参数多元化,让dispatch不再是那个只能传递带type属性的对象了, 此时dispatch不只是dispatch, 它可以接收函数,可以接收promise, 可以接收异步请求,而这都可以通过给applyMiddleware传递对应的处理函数为参数实现不同的action,其目的就是将最终得到的结果传递给原始dispatch, 通过原始dispatch改变store更新数据, 这就是中间件的最终意义。

既然理解了中间件的原理, 那我们不妨简单实现它!

实现createStore, applyMiddleware架构

实现逻辑存放在myRedux文件夹内:

./myRedux
	——applyMiddleware.js  //实现applyMiddleware强化dispatch方法
	——createStore.js      //实现一个简单的createStore方法
	——index.js            //导出
./store
    ——index.js

1. 实现createStore方法:

export default function createStore(renduer, enhancer) {
  //如果添加了中间件就需要增强createStore
  if (enhancer) {
    return enhancer(createStore)(renduer)
  }

  let currentState
  let currentListeners = []

  function getState() {
    return currentState
  }

  function dispatch(action) {
    //修改state
    currentState = renduer(currentState, action)
    // 订阅通知
    currentListeners.forEach(listener => listener())
  }

  function subscribe(listener) {
    //添加订阅
    currentListeners.push(listener)

    return () => {
      currentListeners = []
    }
  }

  // 获取初始值 (原理:没有此type 走default: return state)
  dispatch({type: "REDUX/YYYYYYYYYY"})

  return { getState, dispatch, subscribe }
}

2. 实现applyMiddleware方法:

export default function applyMiddleware(...middlewares) {
  return createStore => reducer => {
    const store = createStore(reducer) //返回原始{ getState, dispatch, subscribe }

    let dispatch = store.dispatch

    const midApi = {
      getState: store.getState,
      dispatch: action => dispatch(action) //dispatch随自身变化 并非是原始的store.dispatch
    }

    //*重点 midApic传递的dispatch是下面增强后的dispatch
    const middlewareChain = middlewares.map(middleware => middleware(midApi))
    dispatch = compose(...middlewareChain)(store.dispatch)

    return { ...store, dispatch }
  }
}

//传入compose(a, b, c)(...args)  返回: a(b(c(...args)))
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)));
}

compose原理是什么, 什么时候该用compose? 答: 当一个值需要经过多个函数才能变成另外一个值,就可以把所有中间件合并成一个函数, 这叫做函数合成(compose)

3. myRedux/index.js来导出模块:

//index.js  
import createStore from "./createStore"
import applyMiddleware from "./applyMiddleware"
export {
  createStore,
  applyMiddleware
}

4. store/index.js文件导出store:

// store/index.js
// 官方redux
// import { createStore, applyMiddleware } from "redux"
// import thunk from "redux-thunk"; // 异步解决方案
// import logger from "redux-logger"; // 打印日志
// import promise from "redux-promise"; // 处理promise

// 自写redux (我们使用自写的myRedux)
import { createStore, applyMiddleware } from "../myRedux"


function countReducer(state = 0, action) {
  switch(action.type) {
    case "ADD":
      return state + (action.payload ?? 1);
    case "MINUS":
      return state - action.payload || 1;
    default:
      return state
  }
}

const store = createStore(
  countReducer,
  applyMiddleware(promise, thunk, logger)
)

export default store


/********下面是自己实现的promise, thunk, logger三个中间件********/

// 以下的所有 action 都为页面 dispatch(action) 传递过来的 action
function logger({dispatch, getState}) {
  return next => action => {
    console.log("++++++++++++++++++++++++++")
    console.log(action.type + "执行了!!!")
    const prevState = getState()
    console.log("prev state", prevState)

    const returnValue = next(action) //因为logger是最后一个参数 所以next是 "dispatch = compose(...middlewareChain)(store.dispatch);" 的store.dispatch (就是原版dispatch)

    const nextState = getState()
    console.log("prev state", nextState)
    console.log("++++++++++++++++++++++++++")

    return returnValue;
  }
}

function thunk({dispatch, getState}) {
  return next => action => {
    console.log("thunk", action)
    // action 数据类型是?对象 | 函数
    if (typeof action === "function") {
      return action(dispatch, getState)
    }
    // 不是 对象|函数 执行next() next是前一个函数的返回函数
    // 比如applyMiddleware(promise, thunk, logger) thunk的前一个函数是logger 所以next是logger内的以action为参数的箭头函数
    return next(action)
  }
}

function promise({dispatch}) {
  function isPromise(obj){
    return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
  }
  return next => action => { //此时这个 action 就是 加强版的 dispatch()内的参数
    console.log("promise", action)
    // 不是promise执行next() next是前一个函数的返回函数 
    // 比如applyMiddleware(promise, thunk, logger) promise的前一个函数是thunk 所以next是thunk内的以action为参数的箭头函数
    return isPromise(action) ? action.then(dispatch) : next(action)
  }
}

5. 页面引入并使用:

// /pages/ReduxPage.js
import React, { Component } from "react"
import store from "../store"

export default class ReduxPage extends Component {
  componentDidMount() {
    //监听store改变刷新页面
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate(); //强制刷新页面
    })
  }
  componentWillUnmount() {
    //组件卸载之前销毁store监听
    if (this.unsubscribe) {
      this.unsubscribe()
    }
  }
  add = () => {
    store.dispatch({type: "ADD"})
  }
  asyAdd = () => {
    store.dispatch((dispatch, getState) => {
      setTimeout(() => {
        dispatch({type: "ADD"})
      }, 1000)
    })
  }
  promiseMinus = () => {
    store.dispatch(
      Promise.resolve({type: "ADD", payload: 3})
    )
  }

  render() {
    return (
      <div>
        <h3>ReduxPage改变的数据count:{store.getState()}</h3>
        <button onClick={this.add}> 同步改变数据 </button>
        <button onClick={this.asyAdd}> 异步请求数据 </button>
        <button onClick={this.promiseMinus}> promise返回数据 </button>
      </div>
    )
  }
}

你们知道真强后的dispatch方法是什么样子吗? 我这有个参考:伪代码如下:

//中间件顺序为 applyMiddleware(promise, thunk, logger) 时compose强化后的dispatch模样:
//内部的 dispatch, getState 就是 midApi 传递进去的函数 dispatch就是强化后的dispatch (内部的dispatch类似递归)
function dispatch(action) {
  console.log("promise", action)
  return isPromise(action) ? action.then(dispatch) : thunkNext(action);

  function thunkNext(action) {
    console.log("thunk", action)
    if (typeof action === "function") {
      return action(dispatch, getState);
    }
    return loggerNext(action)
  }

  function loggerNext(action) {
    const prevState = getState();
    console.log("prev state", prevState)
    const returnValue = next(action);     //最后的这个next就是 store.dispatch
    const nextState = getState();
    console.log("prev state", nextState)
    return returnValue;
  }
}

//页面触发:可带入上面dispatch试试执行流程
//1. 同步改变数据
dispatch({type: "ADD"})

//2. 异步请求触发
dispatch((dispatch, getState) => {
  setTimeout(() => {
    dispatch({type: "ADD"});
  }, 1000);
})

//3. promise返回数据
dispatch(
  Promise.resolve({
    type: "MINUS",
    payload: 100
  })
);

此时我们点击3个按钮会出现什么: 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 applyMiddleware(promise, thunk, logger)内中间件的匹配顺序是:promise → thunk → logger从左往右。 可见当前中间件不符合匹配规则时就会将dispatch内参数传递给后一个中间件去处理, 如果不行就继续向后传递以此类推, dispatch参数如果是一个方法 其内部的dispatch执行又会递归执行dispatch以确保内部dispatch参数被中间件化,最终所有中间件都无法匹配时 才会执行初始传入的原始dispatch更新数据