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

Posted

技术标签:

【中文标题】redux-thunk:错误:动作必须是普通对象。使用自定义中间件进行异步操作【英文标题】:redux-thunk: Error: Actions must be plain objects. Use custom middleware for async actions 【发布时间】:2019-03-04 22:54:33 【问题描述】:

尝试使用 API 创建一个简单的 CRUD 应用。我已经登录和注册工作,所以我知道 redux-thunk 已在商店中正确设置。

我见过很多类似的问题,但没有一个答案能解决我的问题。我知道调度应该返回函数。我很确定情况就是这样。我是新手,对如何调试有点迷茫。 Console.log 没有显示任何内容,所以我使用了警报。我很乐意提供任何其他信息。谢谢寻找。

错误

Error: Actions must be plain objects. Use custom middleware for async actions.
  20 |  componentDidMount() 
  21 |    const  dispatch  = this.props;
  22 |    const  error, rosterMembers, isFetching  = this.props;
  23 |    dispatch(rosterActions.getAll());
  24 | 
  25 |  
  26 | 

容器:RosterListCard.js

import React, PureComponent from 'react';
import PropTypes from 'prop-types';
import  connect  from 'react-redux';

import Card, CardBody, Col, ButtonToolbar from 'reactstrap';
import EmailIcon from 'mdi-react/EmailIcon'
import CheckboxMarkedCircleIcon from 'mdi-react/CheckboxMarkedCircleIcon'
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
import Link from 'react-router-dom';
import  Table  from 'reactstrap';
import  rosterActions  from '../../../../redux/actions';

class RosterListCard extends PureComponent 
  constructor(props) 
    super(props);
//    this.getNoRowsRenderer = this.getNoRowsRenderer.bind(this);
//    this.getRowClassName = this.getRowClassName.bind(this);
  

  componentDidMount() 
    const  dispatch  = this.props;
    const  error, rosterMembers, isFetching  = this.props;
    dispatch(rosterActions.getAll());

  

  componentWillReceiveProps(nextProps) 
    const  dispatch  = nextProps;
    dispatch(rosterActions.getAll());
  

  getNoRowsRenderer() 
    return (
      <div className="noRows">
        No rows
      </div>
    );
  

  render() 
    const  error, rosterMembers, isFetching  = this.props;
    return (
      <div className="container">
        console.log(error)
        console.log(isFetching)
        console.log(rosterMembers)
        error &&
          <div className="alert alert-danger">
            error.message || "Unknown errors."
          </div>
        !isFetching &&
          rosterMembers.length === 0 &&
          <div className="alert alert-warning">Oops, nothing to show.</div>

        rosterMembers &&

          <Table striped>
            <thead>
              <tr>
                <th>Name</th>
                <th>Title</th>
              </tr>
            </thead>
            <tbody>
              this.props.rosterMembers.map((rosterMember) => (
                <tr>
                  <th scope="row">rosterMember.fullName</th>
                  <td>rosterMembers.title</td>
                </tr>
              ))
            </tbody>
          </Table>
        
      </div>
    )
  


RosterListCard.propTypes = 
  rosterMembers: PropTypes.array.isRequired,
  isFetching: PropTypes.bool.isRequired,
  error: PropTypes.object,
  dispatch: PropTypes.func.isRequired
;

function mapStateToProps (state) 

  return 
    error: null,
    isFetching: false,
    didInvalidate: false,
    totalCount: 0,
    rosterMembers: []
  ;


export default connect(mapStateToProps)(RosterListCard);

动作:roster.actions.js

import  sessionService  from 'redux-react-session';
import  rosterConstants  from '../constants';
import  callApi  from "../../utilities/api.utility.js";

export const rosterActions = 
  getAll
;

function rosterRequest() 
  return 
    type: rosterConstants.GETALL_REQUEST
  ;



function rosterSuccess() 
  return function(payload) 
    return 
      type: rosterConstants.GETALL_SUCCESS,
      rosterMembers: payload.items,
      totalCount: payload.total_count
    ;
  ;


function rosterFailure() 
  return function(error) 
    return 
      type: rosterConstants.GETALL_FAILURE,
      error
    ;
  ;


export function getAll() 
  sessionService.loadSession()
    .then(session => 
//      if (typeof session.token === 'undefined') return rosterFailure();
      const url = `$process.env.REACT_APP_API_BASE_URL/Rosters?access_token=$session.token`;
      return callApi(
        url,
        null,
        rosterRequest(),
        rosterSuccess(),
        rosterFailure()
      )
    );

API 模块:api.utility.js

import "isomorphic-fetch";

export function checkStatus(response) 
  if (!response.ok) 
    // (response.status < 200 || response.status > 300)
    const error = new Error(response.statusText);
    error.response = response;
    throw error;
  
  return response;


export function parseJSON(response) 
  return response.json();


/**
 * A utility to call a restful service.
 *
 * @param url The restful service end point.
 * @param config The config object of the call. Can be null.
 * @param request The request action.
 * @param onRequestSuccess The callback function to create request success action.
 *                 The function expects response json payload as its argument.
 * @param onRequestFailure The callback function to create request failure action.
 *                 The function expects error as its argument.
 */
export function callApi(
  url,
  config,
  request,
  onRequestSuccess,
  onRequestFailure
) 
  alert('request');
  return dispatch => 
    alert('request***');
    dispatch(request);
    return fetch(url, config)
      .then(checkStatus)
      .then(parseJSON)
      .then(json => 
        alert('request***');
        dispatch(onRequestSuccess(json));
      )
      .catch(error => 
        alert('request***');
        const response = error.response;
        if (response === undefined) 
          dispatch(onRequestFailure(error));
         else 
          error.status = response.status;
          error.statusText = response.statusText;
          response.text().then(text => 
            try 
              const json = JSON.parse(text);
              error.message = json.message;
             catch (ex) 
              error.message = text;
            
            dispatch(onRequestFailure(error));
          );
        
      );
  ;
  alert('request end');

【问题讨论】:

【参考方案1】:

如果您使用的是redux-thunk,那么您的异步“动作创建者”应该类似于:

export function getAll() 
  return dispatch => 
      sessionService.loadSession()
          .then(session => 
              if (typeof session.token === 'undefined') return dispatch(rosterFailure());
              const url = `$process.env.REACT_APP_API_BASE_URL/Rosters?access_token=$session.token`;
              dispatch(rosterRequest());
              fetch(url, config).then(response => 
                  // validate response and convert to JSON
                  dispatch(rosterSuccess(json));
              
           ).catch(err => 
              // Handle/Transform error
              dispatch(rosterFailure(err));
           
         );
  

另外,我认为您遗漏了有关 redux-thunk 的一些详细信息。异步dispatch 其他操作的操作应返回函数(其中dispatch 是第一个也是唯一需要的参数)。其他动作应该只返回带有type 的典型动作对象以及reducer 函数需要更新其存储的任何其他信息。

例如,您的 rosterRequest 看起来完全符合我的预期,但 rosterSuccess 应该看起来像:

function rosterSuccess(paylod) 
  return 
    type: rosterConstants.GETALL_SUCCESS,
    rosterMembers: payload.items,
    totalCount: payload.total_count
  ;

然后,当您 connect 组件时,您可以 mapDispatchToProps,而不是期望组件直接具有 dispatch 属性。

例如RosterListCard.js的底部:

RosterListCard.propTypes = 
  rosterMembers: PropTypes.array.isRequired,
  isFetching: PropTypes.bool.isRequired,
  error: PropTypes.object,
  getAllRoster: PropTypes.func.isRequired
;

function mapStateToProps (state) 

  return 
    error: null,
    isFetching: false,
    didInvalidate: false,
    totalCount: 0,
    rosterMembers: []
  ;


function mapDispatchToProps(dispatch) 
    return 
        getAllRoster: rosterActions.getAll
    


export default connect(mapStateToProps, mapDispatchToProps)(RosterListCard);

您可以在需要时从this.props 调用该函数:

componentDidMount() 
    const  error, rosterMembers, isFetching  = this.props;
    this.props.getAllRoster();

【讨论】:

以上是关于redux-thunk:错误:动作必须是普通对象。使用自定义中间件进行异步操作的主要内容,如果未能解决你的问题,请参考以下文章

动作必须是普通对象。使用自定义中间件

尝试使用 redux-thunk,但它显示错误,即使对我来说一切都很好

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

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

动作必须是普通对象。反应减少错误

Redux thunk - 错误 · 动作必须是普通对象。使用自定义中间件进行异步操作,即使调度具有键类型的对象