如何学习理解Redux Middleware

2020-01-08 18:44:58 浏览数 (1)

Redux中的middleware其实就像是给你提供一个在action发出到实际reducer执行之前处理一些事情的机会。可以允许我们添加自己的逻辑在这段当中。它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。

加入middleware后,整个数据的流动如下图所示:

举个简单的例子,我们使用middleware将每次action的执行详细信息都打出来。就用官方demo中的todoApp来举例,我们先实现一个简单的reducer用来添加一个todo:

  1. const todoApp = (state = {todo: []}, action) => {
  2. if (action.type === ‘addTodo’) {
  3. state.todo = […state.todo, action.value]
  4. }
  5. return state
  6. }

然后再补上其他逻辑测试,用最原始的方法实现将每次action的执行信息log出来:

  1. const redux = require(‘redux’)
  2. const todoApp = (state = {todo: []}, action) => {
  3. if (action.type === ‘addTodo’) {
  4. state.todo = […state.todo, action.value]
  5. }
  6. return state
  7. }
  8. let store = redux.createStore(todoApp);
  9. const action = {
  10. type: ‘addTodo’,
  11. value: ‘todo’,
  12. }
  13. console.log(‘state: ‘, store.getState());
  14. console.log(‘action: ‘, action);
  15. store.dispatch(action)
  16. console.log(‘next state: ‘, store.getState())

如果不出什么意外的话,我们这段代码应该会成功运行,并且将这段log出这个action的运行情况, 如下图:

接下来我们将log这件事尝试使用redux的middleware来完成。

首先,根据我们之前的了解,middleware其实是一段在action到reducer之间的处理逻辑。我们都知道,标准的一个redux发送一个action是调用store自身的dispatch方法。那么,我们想要在一个action到达reducer之前去做些处理的话,最好的地方应该就是尝试将store的dispatch替换为我们自己的,在其中加上我们的处理逻辑,例如打印log这件事。  

  1. let store = redux.createStore(todoApp);
  2.      const next = store.dispatch;
  3.   const dispatchWithLog = (action) => {
  4.  console.log(‘state: ‘, store.getState());
  5.  console.log(‘action: ‘, action);
  6.  next(action);
  7. console.log(‘next state: ‘, store.getState());
  8.      }
  9.   store.dispatch = dispatchWithLog;

我们将默认store的dispatch替换为自己的dispatchWithLog, 通过这种方式,完成了我们的需求,只要任何地方调用了store的diapatch去发送新的action, 我们都能讲其log出来,这个看起来已经有一点middleware的意思了。

虽然上面已经可以解决问题了,并且已经有点middleware的意思了,但是还有一点硬伤就是,需求多了就比较难搞了,例如就像官方上既需要log又需要Crash Reporting, 再通过这种方式去处理就显得很不优雅。Crash Reporting的middle的一个简单实现如下:

  1. const next1 = store.dispatch;
  2. const dispatchWithCreshReporting = (action) => {
  3. try {
  4. next1(action);
  5. } catch (err) {
  6. console.error(err);
  7. }
  8. }
  9. store.dispatch = dispatchWithCreshReporting;

当然,redux本身给我们提供了包装过后的工具方法来专门应用middleware。其中也不是简单粗暴的替换store上的dispatch了。这个方法即为applyMiddleware。

官方的doc也给出了一个关于applyMiddleware的一个简单粗暴的直接替换dispatch的一个示例,如下:

  1. function applyMiddlewareByMonkeypatching(store, middlewares) {
  2. middlewares = middlewares.slice()
  3. middlewares.reverse()
  4. // Transform dispatch function with each middleware.
  5. middlewares.forEach(middleware =>
  6. store.dispatch = middleware(store)
  7. )
  8. }

关于先逆序middlewares再进行替换,这里主要是为了,让middleware的执行顺序按照我么传给他的array顺序来进行。就像我们上面直接替换的那个例子,越往后面进行替换dispatch的在执行过程中先运行。

当然,官放的具体实现中不是这么简单粗暴的直接替换的方式,因为一来不够优雅,这种方式在链式的调用过程中有可能出现问题。比如某一个middleware并不是同步执行的,这样在进行store.dispatch = middleware(store)就有可能到下一个middleware时,store.dispatch还没有被替换。因此,官方的middleware是接受一个next的参数来,来拿到dispatch,并不是直接从store上对dispatch进行操作的。

一般一个标准的middleware是这个样子的,我们使用最初的log的那个middleware来举例,让它接受一个next(就是一个下一个的dispatch方法),再返回一个dispatch方法。

  1. function logger(store) {
  2. return function(next) {
  3. return function(action) {
  4. console.log(‘state: ‘, store.getState());
  5. console.log(‘action: ‘, action);
  6. let result = next(action);
  7. console.log(‘next state: ‘, store.getState());
  8. return result;
  9. }
  10. }
  11. }
  12. function creshReporting(store) {
  13. return function(next) {
  14. return function(action) {
  15. try {
  16. return next(action);
  17. } catch (err) {
  18. console.error(err);
  19. return next;
  20. }
  21. }
  22. }
  23. }

然后假设我们在apply时这样应用一下:

  1. function applyMiddleware(store, middlewares = [logger, crashReporting]) {
  2. middlewares = middlewares.slice()
  3. middlewares.reverse()
  4. let dispatch = store.dispatch
  5. middlewares.forEach(middleware =>
  6. dispatch = middleware(store)(dispatch)
  7. )
  8. return Object.assign({}, store, { dispatch })
  9. }

这样就能够进行优雅的链式调用了。并且用上ES6箭头函数后,这样写出来会更加的优雅:

  1. const logger = store => next => action => {
  2. console.log(‘state: ‘, store.getState());
  3. console.log(‘action: ‘, action);
  4. let result = next(action);
  5. console.log(‘next state: ‘, store.getState());
  6. return result;
  7. }

最后,其实redux middleware使用起来其实是非常的方便的,只需要记住applyMiddleware这个API即可。即const store = createStore(reducer, applyMiddleware(middlewares))

0 人点赞