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

时间:2018-04-27 09:48:40

标签: redux-thunk

所以我浪费了5个小时。

我有一个像这样的redux thunk动作:

export const fetchUser = () => async (getState, dispatch) => {
  if (getIsFetching(getState().user)) {
    return Promise.resolve();
  }

  dispatch(fetchUserRequest());

  try {
    const response = await api.fetchUser();

    dispatch(fetchUserSuccess({ userObject: { ...response } }));
  } catch (error) {
    dispatch(fetchUserFailure({ message: "Could not fetch user profile." }));
  }
};

调用此内容始终以“操作必须是普通对象。使用自定义中间件进行异步操作。”。

是的,当然。我已经在使用redux-thunk了,为什么它一直在烦我?

注意:fetchUserRequest(),fetchUserSuccess()和fetchUserFailure()都返回简单的普通redux动作。

2 个答案:

答案 0 :(得分:7)

了解此错误消息是了解Redux世界中许多内容的关键。这甚至可能是您将来会问到的面试问题。

实际上,您的动作创建者有两个错误。动作创建者的第一件事是,您的动作创建者应该返回带有type属性和可选的payload属性的普通JavaScript对象,但是目前您不从以下位置返回操作您的动作创建者。

您可能会看到代码编辑器,然后是动作创建者,并且您可能正在思考,您是否正在寻找与我相同的动作创建者?看起来您正在返回具有type属性的对象,但实际上您没有。

即使看起来您正在返回一个JavaScript对象,事实并非如此。

我们在编辑器中编写的许多代码是ES2015、2016、2017、2018等。您和我编写的代码都将转换为es2015语法,这就是在浏览器内部实际执行的代码。

因此,即使此函数看起来像返回带有type属性的对象,实际上,在将其转换为es2015代码后,我们却没有。

下次将您的异步操作创建者拖放到babeljs.io中,您将明白我的意思。

这实际上是将我们的代码向下移植到ES2015。

因此,在代码编辑器内部,您认为自己正在执行编写的代码,但是实际上,因为您特别具有这种async / await语法,所以整个功能都扩展到了babeljs右侧。 io。

因此,当我告诉您动作创建者没有返回普通的JavaScript对象时,它的原因是您具有异步/等待语法。这就是为什么您的动作创建者无法按预期工作的原因。

因此,您最初返回的不是您的操作对象,而是您返回的对象。因此,当您的动作创建者第一次被调用时,您不会返回该动作对象,而是如您所见,在其中包含了一些代码来返回您的请求对象。这就是返回请求的内容。因此,您从动作创建者那里返回了请求,该请求进入了store.dispatch方法中。

然后,redux存储查看返回的内容并说好,这是仅具有type属性的普通JaavaScript对象吗?好吧,在这种情况下,不,因为我们只是返回了请求对象,所以没有返回操作,这就是为什么我们最终看到讨厌的红色消息,说操作必须是普通对象。因此,我们没有返回普通对象,而操作必须返回普通对象。我们返回了一个可能分配了一些奇特方法的请求对象,并且可能没有type属性,因此我们绝对没有分派我们认为分派的东西。

所有这些都是由于您使用的async / await语法。

因此,这是您的动作创建者的第一个问题。由于使用了被转换为es5代码的async / await语法,因此,您在浏览器中实际运行的不是您实际认为的运行。

因此,我们正在调度NOT Redux动作,正在调度Redux不关心的随机对象。

那么,我们如何正确利用称为Redux-Thunk的中间件?在我们回答这个问题之前,让我们了解一下Redux世界中的中间件。

中间件是普通的JavaScript函数,将在我们分派的每个操作中调用该中间件。在该功能内部,中间件可以阻止调度动作,阻止其进入任何化简器,修改动作或以任何方式,形状或形式操纵动作。

Redux-Thunk是最受欢迎的中间件,因为它可以帮助我们与异步动作创建者一起工作。

好的,那么Redux-Thunk如何帮助我们解决这个问题?

好吧,Redux-Thunk将放宽常规的动作创建者规则或Redux,就像我在上面所说的那样,动作创建者必须返回动作对象,它必须具有type属性,并且可选地, payload属性。

Redux-Thunk没有内在的本质,它允许我们做很多事情,其中​​之一是处理动作创建者,但这不是其主要目的。

一旦动作创建者参与到Redux-Thunk中,它就可以返回普通对象或返回函数。

你知道这是怎么回事吗?

那么返回函数有什么帮助?

因此,我们的动作创建者以对象或函数的形式返回“动作”。该“动作”将被发送到调度功能,最终将在Redux-Thunk内部结束。

Redux-Thunk会说:“嗨,行动,您是函数还是对象?如果“操作”告诉Redux-Thunk它的对象,则Redux-Thunk会说:“好吧,感谢您停止操作,但是我更喜欢只处理函数”,然后Redux-Thunk会将“ action”推向减速器。

否则,Redux-Thunk会说:“哦,您是一个函数吗?不错!”然后,Redux-Thunk将调用您的函数,并将dispatchgetState函数作为参数传递。您已经获得答案的语法版本,所以请允许我提供它的一个变体。

因此,不仅如此:

export const fetchPosts = async () => {
  const response  = await jsonPlaceholder.get('/posts');
  return {
      type: 'FETCH_POSTS',
      payload: response
  }
};

使用Redux-Thunk,您将包括以下内容:

export const fetchPosts = async () => {
  return function(dispatch, getState) {
    const response  = await jsonPlaceholder.get('/posts');
    return {
        type: 'FETCH_POSTS',
        payload: response
    }
  }
};

现在在上面的示例中,我正在使用动作创建者向外部API发出异步请求。因此,dispatch具有在我们应用程序的Redux端更改数据的无限权力。

您看到我利用getState,因此您也可以理解,除了dispatch之外,getState还将返回商店中的所有数据。这两个参数在Redux应用程序内部具有无限的功能。通过dispatch,我们可以更改所需的任何数据;通过getState,我们可以读取所需的任何数据。

转到Redux-Thunk本身的源代码: https://github.com/reduxjs/redux-thunk/blob/master/src/index.js

以上是Redux-Thunk的全部内容。只有6到7行可以执行任何操作,其他则是初始化步骤,函数声明和导出。第2行是一系列返回函数的函数。

在它的主体中,您看到发生了什么的逻辑并询问它,您是否调度并执行了操作,如果是,则是操作还是功能?

我上面描述的所有内容都被捕获在源代码中。

因此,对于我来说,正确地将Redux-Thunk应用于我给您的示例中,我将转到根index.js文件,然后将其安装在终端中后将其导入,如下所示:

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';

import App from "./components/App";
import reducers from "./reducers";

ReactDOM.render(
  <Provider store={createStore(reducers)}>
    <App />
  </Provider>,
  document.querySelector("#root")

注意,我也导入了applyMiddleware。此功能是我们将中间件连接到Redux的方式。

因此,然后我将createStore预先应用到名为store的变量中,并在Provider存储中实现该变量,如下所示:

const store = createStore(reducers);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector("#root")
);

要连接Redux-Thunk,作为第二个参数,我将调用applyMiddleware并像这样传递thunk

const store = createStore(reducers, applyMiddleware(thunk));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector("#root")

然后在动作创建者内部进行一两个更改。我仍然可以返回带有类型属性的普通对象,这是一个选择,使用Redux-Thunk,我们仍然可以使普通的动作创建者返回对象,但是我们不必返回动作。

因此,除了返回操作之外,我可以调用dispatch并像这样传递我的操作对象:

export const fetchPosts = () => {
  return async function(dispatch, getState) {
    const response  = await jsonPlaceholder.get('/posts');

    dispatch({type: 'FETCH_POSTS', payload: response })
  }
};

在Redux-Thunk中,我们可以使用async / await语法,因为该语法只会修改内部函数的返回值。该功能一无所获。 Redux-Thunk将无法获得返回内容的引用并加以利用,我们可以返回还是不返回,我们从外部函数返回的内容就是我们所关心的。

重构上面我刚刚分享的内容的一种常见方式如下:

export const fetchPosts =()=> {   返回异步(发送)=> {     const response = await jsonPlaceholder.get('/ posts');

dispatch({type: 'FETCH_POSTS', payload: })

} };

因此,如果您不使用函数中的getState,则可以将其作为参数保留。您可以像这样使代码更简洁:

export const fetchPosts = () => async dispatch => {
    const response  = await jsonPlaceholder.get('/posts');

    dispatch({type: 'FETCH_POSTS', payload: response })
} 

您将在很多Redux项目中看到这一点。就是这样。

答案 1 :(得分:5)

export const fetchUser = () => async (getState, dispatch) => {// your code here}

需要

export const fetchUser = () => async (dispatch, getState) => {// your code here}

(getState,dispatch)!==(发送 getState

虽然我很高兴我最终解决了这个问题,但那里的方式非常令人沮丧。