使用redux过程中遇到Actions must be plain objects. Use custom middleware for async actions.异常的分析

原文
原因一:使用异步actions时,没有配置redux-thunk这个中间件
中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);

上面代码中,redux-logger提供一个生成器createLogger,可以生成日志中间件logger。然后,将它放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。

const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);

applyMiddleware 是Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。

原因二:调用action方法时,方法体内部并没有调用dispatch。

/**
 * 加载路由或iframe
 *
 * @param menu      需要加载的菜单
 */
export function onLoadMain(menu) {
    let routeUrl = menu.routeurl;
    let url = menu.url;
    console.log("routeUrl : ",routeUrl);
    if (routeUrl) {
        weaHistory.push({pathname: routeUrl});

        document.getElementById('e9frameMain').style.visibility = 'hidden';
        document.getElementById('e9routeMain').style.display = 'block';
        // let mainframe = document.getElementById('mainFrame');
        // mainframe.src = 'about:blank';
        document.getElementById('mainFrame').src = 'about:blank';
    } else if (url && url != 'javascript:void(0);') {
        let target = menu.target || 'mainFrame';
        if ('mainFrame' != target) {
            window.open(url, target);
        } else {
            // let mainframe = document.getElementById('mainFrame');
            // mainframe.src = url;
            document.getElementById('mainFrame').src = url;
            document.getElementById('e9frameMain').style.visibility = 'visible';
            document.getElementById('e9routeMain').style.display = 'none';
        }
    }
}

以上是一个普通的function,放在action的js文件中,如果你使用了如下方式去调用这个方法,虽然能成功调用,但在配置了redux-thunk这个中间件的情况下,你发起的任何action方法,都会走thunk这个中间件,一旦方法体内没有dispatch这个方法,则会报Actions must be plain objects. Use custom middleware for async actions这个异常。

import * as themeActions from '../../../actions/theme';

handleClick(e) {

        const {actions}=this.props;
        let urlParams= {
            iframeUrl: e.key,
            routeUrl:e.item.props.routeurl
        }

        actions.updateMenusUrl(urlParams);*/
        let menuInfo={
            routeurl:e.item.props.routeurl,
            url: e.key,
            target:'mainFrame'
        }
        //通过派生action的形式来调用这个方法
        actions. onLoadMain(menuInfo);

function mapStateToProps(state) {
    const {middleTheme} = state;

    return {
        backEndMenuUrl: middleTheme.get('backEndMenuUrl'),
        columnMenuInfo:middleTheme.get('columnMenuInfo')
    }
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(themeActions, dispatch)
    };
}

module.exports = connect(mapStateToProps, mapDispatchToProps)(E9LeftMenusTree);

所以常规function最好通过import导入的形式去调用就OK了,如下:

import {onLoadMain} from '../../../actions/theme';

handleClick(e) {

        const {actions}=this.props;
        let urlParams= {
            iframeUrl: e.key,
            routeUrl:e.item.props.routeurl
        }

        actions.updateMenusUrl(urlParams);*/
        let menuInfo={
            routeurl:e.item.props.routeurl,
            url: e.key,
            target:'mainFrame'
        }
        //常规import组件形式调用function
        onLoadMain(menuInfo);
    }

最后分享下我在解决这个问题中参考的阮大大的关于redux中间件的介绍:Redux中间件和异步操作

Redux Example



react-pxq

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'

// React component
class Counter extends Component {
  render() {
    const { value, onIncreaseClick } = this.props
    return (
      <div>
        <span>{value}</span>
        <button onClick={onIncreaseClick}>Increase</button>
      </div>
    )
  }
}

Counter.propTypes = {
  value: PropTypes.number.isRequired,
  onIncreaseClick: PropTypes.func.isRequired
}

// Action
const increaseAction = { type: 'increase' }

// Reducer
function counter(state = { count: 0 }, action) {
  const count = state.count
  switch (action.type) {
    case 'increase':
      return { count: count + 1 }
    default:
      return state
  }
}

// Store
const store = createStore(counter)

// Map Redux state to component props
function mapStateToProps(state) {
  return {
    value: state.count
  }
}

// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => dispatch(increaseAction)
  }
}

// Connected Component
const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)