欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

用于反应状态管理的 redux 中间件的使用和实现。

最编程 2024-04-11 20:45:43
...

redux 一个强大的功能之一就是可以使用 redux middleware 中间件机制在dispatch actionaction到达reducer之间提供一个逻辑的插入点,可以执行想要执行的逻辑,比如将action写入日志、埋点的上报等等。

redux middleware是基于洋葱模型,中间件的控制权从第一个中间件交到下一个中间件直到最后一个中间件,紧接着控制权再从最后一个中间件交由上一个中间件直到第一个中间件,至此middleware的流程走完。

onion.jpeg

使用 middleware

首先我们以一个最简单的 redux 计数器为例子,在其中使用 redux-thunkredux-logger 中间件:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { createStore, bindActionCreators, combineReducers, applyMiddleware } from 'redux'

import logger from 'redux-logger';
import thunk from 'redux-thunk';

export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';

const increment = () => ({
  type: INCREMENT
})

const decrement = () => ({
  type: DECREMENT
})

export const asyncIncrement = () => (dispatch: any) => {
  setTimeout(() => {
    dispatch(increment())
  }, 1000);
}

const actionCreators = { increment, decrement }

interface CounterState {
  value: number
}

const counterInitState: CounterState = {
  value: 0,
}

const counterReducer = (state = counterInitState, action: any): CounterState => {
  switch (action.type) {
    case INCREMENT:
      return { value: state.value + 1 };
    case DECREMENT:
      return { value: state.value - 1 };
    default:
      return state;
  }
}

interface TodoState {
  todos: string[]
}

const todoInitState: TodoState = {
  todos: []
}

const ADD_TODO = 'ADD_TODO'

const addTodo = () => ({ type: ADD_TODO });

const todoActionsCreators = { addTodo }

const todoReducer = (state = todoInitState, action: any): TodoState => {
  switch (action.type) {
    case ADD_TODO:
      return { todos: ['Use Redux'] };
    default:
      return state;
  }
}

const rootReducer = combineReducers({
  counter: counterReducer,
  todo: todoReducer
})

// 使用中间件
export const store = applyMiddleware(thunk as any, logger as any)(createStore)(rootReducer);
export const boundActions = bindActionCreators(actionCreators, store.dispatch);
export const todoActions = bindActionCreators(todoActionsCreators, store.dispatch);

store.subscribe(render);

function render() {
  ReactDOM.render(
    (<App />),
    document.getElementById('root')
  );
}

render();

可以看到使用redux middleware十分简单,只需要调用 redux 暴露的 applyMiddleware 函数并传入需要使用的 middleware 即可

自定义redux middleware

下面我们来看如何实现自定义redux middleware并在redux工程中使用自定义的middleware

一个中间件的格式形如:

const middleware = (store) => (next) => (action) => {
  // do something before state change...
  next(action)
  // do something after state change...
}

其中可以通过 store 访问 getStatedispatch 方法(改写过后的), 因此可以在中间件中获取 store中的数据并且调用 dispatch 来派发 action

next 用于执行下一个中间件, 这是必须调用的, 如果不调用 next 那么整个 dispatch 的流程就会中断也就是说某个动作派发之后并不会走到reducer从而改变state, 而在最后一个中间件中 next 方法就是真正的 store.dispatch。所以在某个中间件中 next 函数调用前的逻辑即代码是整个 state 还未改变之前执行的, 而在 next 函数调用后的逻辑即代码实在 state 改变后执行的。

action 既为某个动作派发之后传入到 dispatch 中的 action

下面看一下中间件的执行过程和结果, 理解洋葱模型的含义, 自定义三个 middleware:

const middleware1: IMiddleware = () => (next) => (action) => {
  console.log(`middle execute!`);
  next(action)
  console.log(`middle quit!`);
}

const middleware2: IMiddleware = () => (next) => (action) => {
  console.log(`middle2 execute!`);
  next(action)
  console.log(`middle2 quit!`);
}

const middleware3: IMiddleware = () => (next) => (action) => {
  console.log(`middle3 execute!`);
  next(action)
  console.log(`middle3 quit!`);
}

在 redux 中使用:

const store = applyMiddleware(middleware1, middleware2, middleware3)(createStore)(rootReducer);

当某个action被派发时,中间件的执行逻辑如下:

1650279891151-8aaa1602-69b8-4660-a4fd-6b53e39c630c.jpeg

可见整个middleware的执行顺序是 middleware1 执行之后调用 next 进入到 middleware2, middleware2 调用 next 进入到 middleware3, middleware3 调用 next (这里实际上是 dispatch) 之后 middleware3 执行完毕紧接着再依次的将 middleware2middleware1 执行完毕。洋葱模型可见一斑!

实际上很多有中间件的框架都是洋葱模型,比如 express,koa 等,在日常项目中如果用到中间件实际上也可以使用这个模型

实现 redux middleware

我们使用middleware时调用的就是 reduxapplyMiddleware 方法, 下面实现以下 applyMiddleware 方法:

import { CreateStoreType } from "./createStore";
import compose from './compose'

interface IMiddlewareAPI {
  dispatch: (...args: any[]) => any;
  getState(): any;
}

export interface IMiddleware {
  (api: IMiddlewareAPI): (next: Function) => (action: any) => any;
}

export default function applyMiddleware(...middlewares: IMiddleware[]) {
  return function (createStore: CreateStoreType) {
    return function (reducer: Function) {
      const store = createStore(reducer);

      let dispatch = (...args: any[]) => {
        throw new Error(
          'Dispatching while constructing your middleware is not allowed. ' +
            'Other middleware would not be applied to this dispatch.'
        )
      }
        
      // 这个就是我们在自定义中间件中能够访问的两个 API
      const middlewareAPI: IMiddlewareAPI = {
        getState: store.getState,
        dispatch: (...args: any[]) => dispatch(...args),
      }
      
      // 给每个中间件注入能访问的 middlewareAPI
      const chain = middlewares.map((middleware) => middleware(middlewareAPI))
      
      // 将所有的中间件组合成一个chain 链
      dispatch = compose(...chain)(store.dispatch);

      return {
        ...store,
        dispatch
      }
    }
  }
}

applyMiddleware 方法接受一系列的 middleware 作为参数, 首先将 middleware 传入 middlewareAPI 执行一次得到一个 chain 数组, 此时 chain 数组里的元素是这样:

// 原来的 middleware
(store) => {
  (next) => {
    (action) => {
      // do something...
      next(action);
    }
  }
}

// 执行 middleware(middlewareAPI) 之后, chain 数组中的元素
(next) => {
  (action) => {
    // do something...
    // 此时函数内部就可以访问这个 store 对象了,
    next(action);
  }
}

下面来看一下这个重要的 compose 函数:

export default function compose(...funcs: Function[]) {
  if (funcs.length === 0) return (arg: any) => arg;

  if (funcs.length === 1) return funcs[0]

  return funcs.reduce((a, b) => {
    return (...args: any) => a(b(...args))
  })
}

compose 函数的作用是将一系列的函数转化为一个复合函数, 复合函数是将一个函数的输出作为另一个函数的输入的函数, 这个 compose 方法将 funcs 这些函数中从 funcs 末尾到开头, 依次将后面函数的执行结果作为前一个函数的参数从而转换成一个复合函数。 compose 最终返回一个函数, 该函数接收任意的参数, 函数体中会从头到尾依次执行 funcs 中的函数, 并将后一个函数的执行结果作为参数传递给前一个函数。

const chain = middlewares.map((middleware) => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch);

结合 applyMiddleware 方法来看, composechain 函数数组转换成一个复合函数, 接着调用这个复合函数传入参数 store.dispatch, 所以 chain 中最后一个元素(也就是最后一个中间件)接收的 next 方法就是 store.dispatch。这个复合函数执行之后会返回一个函数, 该函数就是 enhanser 之后的 dispatch 方法。这个dispatch形如:

(action) => {
  // do something
  next(); // 此时这个 next 就是下一个 chain 数组中的返回值, 也是形如 (action) => { next() } 的这么一个函数
}

所以当在代码中调用 store.dispatch 时会依次按照顺序执行 middleware 函数, 如果 middleware 中调用了 next 函数, 那么就会执行下一个 middleware 函数直到执行到最后一个middleware 此时的 next 方法就是真正的 dispatch 方法, 接着去改变state中的状态, 接着最后一个 middleware 执行完毕, 即上一个 middlewarenext 函数调用执行完毕, 接着上一个 middleware 执行完毕, 直到第一个 middleware 执行完毕。

redux-middleware.png

总结

以上就是 redux middleware 的使用与实现原理了, 实际上middleware的实现目的就是就是使得每一个 middleware 都能够执行并且能够在中间件中控制在 reducer 之前和之后都能够插入需要执行的逻辑。

本文介绍了 redux middleware 的实现原理, 下一篇文章redux saga 的使用与实现原理将介绍 redux 非常重要的副作用处理工具 redux-sage 的使用与实现原理。

推荐阅读