自定义 Redux 中间件 - 调度到中间件链的开头?

Posted

技术标签:

【中文标题】自定义 Redux 中间件 - 调度到中间件链的开头?【英文标题】:Custom Redux Middleware - Dispatch to beginning of middleware chain? 【发布时间】:2017-07-02 05:23:10 【问题描述】:

我正在编写一个需要调度 thunk 操作的自定义中间件。问题是在中间件链中redux-thunk之后调用了中间件,所以在使用提供的dispatch时出现错误Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.

export default function createMiddleware() 
    return (dispatch, getState) => next => (action) => 
        if(action.type !== 'FOO') 
            return next(action);
        

        dispatch(thunkActionHere); // this is the issue
    

我想将这个 thunk 动作分派回中间件链的开头,以便 redux-thunk 可以处理它。这可能吗?

更新:

function createMiddleware(extraArgument) 
    return function (dispatch, getState) 
        return function (next) 
            return function (action) 
                switch (action.type) 
                    case 'FOO1':
                        dispatch(type: 'NORMAL_ACTION'); // works fine
                        break;
                    case 'FOO2':
                        dispatch(function() 
                            return (dispatch, getState) =>  // Error: Actions must be plain objects. Use custom middleware for async actions.
                                console.log('inside the thunk');
                            ;
                        );
                        break;
                    default:
                        return next(action);
                
            ;
        ;
    ;


const middleware = createMiddleware();
middleware.withExtraArgument = createMiddleware;

export default middleware;

这是我的商店配置:

export default function configureStore(initialState) 
    const store = createStore(rootReducer, initialState, compose(
        // Add other middleware on this line...
        applyMiddleware(bugsnagErrorCatcherMiddleware()),
        applyMiddleware(thunk.withExtraArgument(APIFactory, PusherManager)),
        applyMiddleware(webrtcVideoMiddleware.withExtraArgument(PusherManager)), // this is the middleware above
        applyMiddleware(bugsnagbreadcrumbLoggerMiddleware()),
        )
    );

    return store;

我不能将我的中间件放在 redux-thunk 之前,因为这样它就不会接收到 thunk 调度的操作。

【问题讨论】:

【参考方案1】:

在中间件链内部调度会将动作发送到中间件链的开头,并像往常一样调用 thunk(Demo - 查看控制台)。

为什么?

原来的store.dispatch()(在应用中间件之前)检查动作是否是普通的POJO,如果不是则抛出错误:

  function dispatch(action) 
    if (!isPlainObject(action)) 
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    

当你 applyMiddleware() 时,dispatch 被一个新的方法取代,即中间件链,最终调用原来的 store.dispatch()。在applyMiddleware方法中可以看到:

export default function applyMiddleware(...middlewares) 
  return (createStore) => (reducer, preloadedState, enhancer) => 
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch // dispatch is now the original store's dispatch
    let chain = []

    const middlewareAPI = 
      getState: store.getState,
      dispatch: (action) => dispatch(action) // this refers to the dispatch variable. However, it's not the original dispatch, but the one that was created by compose
    
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch) // dispatch is a composition of the chain, with the original dispatch in the end

    return 
      ...store,
      dispatch
    
  

顺便说一句 - 将您的中间件更改为此,因为第一个功能会阻止您的中间件工作。

export default const createMiddleware = (dispatch, getState) => next =>   (action) => 
    if(action.type !== 'FOO') 
        return next(action);
    

    dispatch(thunkActionHere); // this is the issue

【讨论】:

原来问题出在我的商店配置中。使用 redux 的 compose 会导致问题。 您可以将其添加为另一个答案吗?这很有趣。 肯定的,刚刚做了【参考方案2】:

原来问题出在我的商店配置中。使用 redux 的 compose 会导致问题。

之前:

import createStore, applyMiddleware, compose from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../redux/reducers';
import webrtcVideoMiddleware from '../redux/middleware/webrtcVideo';
import bugsnagErrorCatcherMiddleware from '../redux/middleware/bugsnag/errorCatcher';
import bugsnagbreadcrumbLoggerMiddleware from '../redux/middleware/bugsnag/breadcrumbLogger';
import * as APIFactory from '../services/APIFactory';
import Pusher from '../services/PusherManager';

const PusherManager = new Pusher(false);

export default function configureStore(initialState) 
    return createStore(rootReducer, initialState, compose(
        applyMiddleware(bugsnagErrorCatcherMiddleware()),
        applyMiddleware(thunk.withExtraArgument(APIFactory, PusherManager)),
        applyMiddleware(webrtcVideoMiddleware(PusherManager)),
        applyMiddleware(bugsnagbreadcrumbLoggerMiddleware())
    ));

之后:

import createStore, applyMiddleware from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../redux/reducers';
import webRTCVideoMiddleware from '../redux/middleware/webrtcVideo';
import bugsnagErrorCatcherMiddleware from '../redux/middleware/bugsnag/errorCatcher';
import bugsnagBreadcrumbLoggerMiddleware from '../redux/middleware/bugsnag/breadcrumbLogger';
import * as APIFactory from '../services/APIFactory';
import Pusher from '../services/PusherManager';

const PusherManager = new Pusher(false);

export default function configureStore(initialState) 
    const middleware = [
        bugsnagErrorCatcherMiddleware(),
        thunk.withExtraArgument(APIFactory, PusherManager),
        webRTCVideoMiddleware.withExtraArgument(PusherManager),
        bugsnagBreadcrumbLoggerMiddleware(),
    ];

    return createStore(rootReducer, initialState, applyMiddleware(...middleware));

【讨论】:

【参考方案3】:

我们使用来自redux-devtools-extension 的composeWithDevTools。与上述相同的问题和相同的解决方案。只需要转而使用applyMiddleware(...middlewares) 而不是多个applyMiddleware(middleware), applyMiddleware(middleware) 作为组合的参数。

【讨论】:

以上是关于自定义 Redux 中间件 - 调度到中间件链的开头?的主要内容,如果未能解决你的问题,请参考以下文章

Redux Thunk vs Redux 自定义中间件

Redux 错误操作必须是普通对象。使用自定义中间件进行异步操作

React Redux - 动作必须是普通对象。使用自定义中间件进行异步操作

React 和 Redux:管理 Redux 自定义中间件列表

ReactJS + Redux + Reduc-thunk 无法调度承诺

动作必须是普通对象。将自定义中间件用于测试库而不是应用程序上的异步操作