我如何解决“操作必须是普通对象.使用自定义中间件进行异步操作.]"? [英] How do I resolve "Actions must be plain objects. Use custom middleware for async actions.]"?

查看:49
本文介绍了我如何解决“操作必须是普通对象.使用自定义中间件进行异步操作.]"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我在这上面浪费了 5 个小时.

So I have wasted 5 hours on this.

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

I have a redux thunk action like this:

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." }));
  }
};

调用这个总是在 Actions 必须是普通对象.将自定义中间件用于异步操作.].

是的,当然.我已经为此使用了 redux-thunk,为什么它一直困扰着我?

Yeah, sure. I'm already using redux-thunk for that, why does it keep bugging me?

注意:fetchUserRequest()fetchUserSuccess()fetchUserFailure() 都返回简单、简单的 redux 操作.

NOTE: fetchUserRequest(), fetchUserSuccess() and fetchUserFailure() all return simple, plain redux actions.

推荐答案

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

Understanding this error message is key to understanding a lot of stuff in the world of Redux. This may even be an interview question you get asked in the future.

实际上,您的动作创建者有两个问题.你的动作创建者的第一件事是你的动作创建者应该返回带有 type 属性和可选的 payload 属性的纯 JavaScript 对象,但目前你没有从您的动作创建者返回动作.

In reality, there are two things wrong with your action creator. The first thing wrong with your action creator is that your action creator is supposed to return plain JavaScript objects with a type property and optionally a payload property as well, but at present you are not returning an action from your action creator.

您可能会查看您的代码编辑器和操作创建器,并且您可能会想,您是否和我在查看相同的操作创建器?看起来您返回的对象可能具有 type 属性,但实际上并非如此.

You might look at your code editor and look at the action creator and you may be thinking, are you looking at the same action creator as I am? It might look like you are returning an object with a type property, but in fact you are not.

尽管看起来您返回的是一个 JavaScript 对象,但事实并非如此.

Even though it looks like you are returning a JavaScript object, that is not the case.

我们在编辑器中编写的大部分代码都是 ES2015、2016、2017、2018 等.你和我写的代码被转换成 es2015 语法,这就是在浏览器中实际执行的内容.

Much of the code we write inside our editor is ES2015, 2016, 2017, 2018 and so on. The code you and I write gets transpiled down to es2015 syntax and thats what actually gets executed inside the browser.

所以即使这个函数看起来像是返回一个带有 type 属性的对象,但实际上,在我们将其转换为 es2015 代码后,我们不是.

So even though this function looks like its returning an object with a type property, in fact, after we transpile this to es2015 code, we are not.

下次把你的异步动作创建器放到 babeljs.io 中,你就会明白我的意思.

Drop your asynchronous action creator into babeljs.io next time and you will see what I mean.

这实际上是将我们的代码转换为 ES2015.

This is what is actually transpiling our code down to ES2015.

所以在代码编辑器中,您认为您正在执行您编写的代码,但实际上,因为您专门使用了这种 async/await 语法,所以整个功能会扩展到您在 babeljs 右侧看到的内容.

So inside of the code editor, you think you are executing the code you wrote but in fact, because you specifically have this async/await syntax, the entire function gets expanded to what you see on the right hand side of babeljs.io.

所以当我告诉你你的动作创建者没有返回一个普通的 JavaScript 对象时,这是因为你有 async/await 语法.这就是您的操作创建器未按预期工作的原因.

So when I say to you that your action creator is not returning a plain JavaScript object, its because you have that async/await syntax. Thats why your action creator is not working as expected.

所以你返回,而不是最初调用时的动作对象.当你的动作创建者第一次被调用时,你不会返回动作对象,相反,正如你所看到的,你有一些代码可以返回你的请求对象.这就是返回的内容——一个请求.你从你的动作创建者那里返回请求,然后进入 store.dispatch 方法.

So you return, not your action object when this is initially called. When your action creator gets called for the first time, you do not return the action object, instead, as you saw, you have some code inside that returns your request object. That is what gets returned -- a request. You return the request from your action creator and that goes into the store.dispatch method.

然后 redux store 查看返回的内容并说好的,这是一个只有 type 属性的普通 JavaScript 对象吗?好吧,在这种情况下,不,因为我们刚刚返回了请求对象,我们没有返回我们的操作,这就是为什么我们最终看到令人讨厌的红色消息,说操作必须是普通对象.所以我们没有返回一个普通对象,动作必须返回一个普通对象.我们返回了一个请求对象,该对象可能分配了一些奇特的方法,并且可能不是 type 属性,所以我们肯定没有调度我们认为我们正在调度的内容.

Then the redux store looks at what was returned and says okay, is this a plain JavaScript object with only a type property? Well, in this case, no because we just returned the request object we did not return our action and thats why we ended up seeing the nasty red message saying Actions must be plain objects. So we did not return a plain object and actions must return plain objects. We returned a request object that probably has some fancy methods assigned to it and probably not a type property, so we definitely did not dispatch what we thought we were dispatching.

这完全是因为您使用的 async/await 语法.

This is all because of the async/await syntax that you are using.

这是您的动作创建者的第 1 个问题.由于使用 async/await 语法将其转换为 es5 代码,因此浏览器中实际运行的内容并不是您认为实际运行的内容.

So that is issue number 1 with your action creator. As a result of using the async/await syntax which gets transpiled down to es5 code, what actually runs inside your browser is not what you think actually runs.

所以我们正在调度一个 NOT Redux 操作,我们正在调度一个 Redux 不关心的随机对象.

So we are dispatching a NOT Redux action, we are dispatching a random object that Redux does not care about.

那么我们如何正确使用这个叫做 Redux-Thunk 的中间件呢?在回答这个问题之前,让我们先了解一下 Redux 世界中的中间件是什么.

So how do we properly make use of this middleware called Redux-Thunk? Before we answer that, let's understand what a middleware is in the world of Redux.

中间件是一个普通的 JavaScript 函数,它会在我们分派的每个动作中被调用.在该函数内部,中间件有机会阻止一个动作被分派,阻止它进入任何减速器,修改一个动作或以任何方式、形状或形式操纵一个动作.

A middleware is a plain JavaScript function that will be called with every single action that we dispatch. Inside that function, a middleware has the opportunity to stop an action from being dispatched, prevent it from going to any reducers, modify an action or manipulate an action in any way, shape or form.

Redux-Thunk 是最受欢迎的中间件,因为它可以帮助我们与异步操作创建者合作.

Redux-Thunk is the most popular middleware, because it helps us work with asynchronous action creators.

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

Okay, so how does Redux-Thunk help us solve this problem?

好吧,Redux-Thunk 将放宽正常的动作创建者规则或 Redux,正如我上面所说,动作创建者必须返回动作对象,它必须具有 type 属性和可选,payload 属性.

Well, Redux-Thunk will relax the normal action creator rules or Redux which says, as I have been saying above, that an action creator must return action objects, it must have a type property and optionally, a payload property.

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

There is nothing intrinsic about Redux-Thunk, it allows us to do many things, one of them being handling action creators, but its not its primary purpose.

一旦我们的动作创建器包含 Redux-Thunk,它就可以返回普通对象或返回函数.

Once we have Redux-Thunk involved in our action creator it can return plain objects OR it can return functions.

你明白这是怎么回事了吗?

You see where this is going?

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

So how does returning a function help?

所以我们的动作创建者返回一个动作"以对象或函数的形式.那个动作"将被发送到调度函数,最终它会在 Redux-Thunk 中结束.

So our action creator returns an "action" in the form of an object or function. That "action" will be sent to the dispatch function and eventually it will end up inside of Redux-Thunk.

Redux-Thunk 会说,动作,你是一个函数还是一个对象?";如果动作"告诉 Redux-Thunk 它是一个对象,Redux-Thunk 会说,好吧,谢谢你停下来,行动,但我更喜欢只处理函数";然后 Redux-Thunk 将推送action"朝向减速器.

Redux-Thunk will say, "hi action, are you a function or are you an object?" If the "action" tells Redux-Thunk its an object, Redux-Thunk will say, "well, thanks for stopping by, action, but I prefer to only deal with functions" and then Redux-Thunk will shove "action" towards the reducers.

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

Otherwise, Redux-Thunk will say, "oh so you are a function? Nice!" Redux-Thunk will then invoke your function and it passes the dispatch, getState functions as arguments. You were already given the syntax version of your answer, so allow me to offer a variation of it.

所以而不仅仅是这样:

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

在 Redux-Thunk 中,您将包含以下内容:

with Redux-Thunk you would include this:

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

现在在上面的示例中,我正在使用我的操作创建者向外部 API 发出异步请求.所以这个 dispatch 有无限的权力来改变我们应用程序 Redux 端的数据.

Now in the above example I am making an asynchronous request with my action creator to an outside API. So this dispatch has unlimited powers to change the data on the Redux side of our application.

你看到我使用了 getState 所以你也可以理解除了 dispatch 之外,getState 将返回你的所有数据店铺.这两个参数在我们的 Redux 应用程序中具有无限的力量.通过dispatch我们可以改变我们想要的任何数据,通过getState我们可以读取我们想要的任何数据.

You see me utilizing getState so you can also understand that in addition to dispatch, getState will return all of the data inside of your store. These two arguments have unlimited power inside our Redux application. Through dispatch we can change any data we want and through getState we can read any data we want.

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

Go to the source code of Redux-Thunk itself: https://github.com/reduxjs/redux-thunk/blob/master/src/index.js

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

The above is all of Redux-Thunk. Only 6 to 7 lines do anything, the others are initialization steps, function declarations and export. On line 2 is a series of functions that return functions.

在它的正文中,你看到了正在发生的事情的逻辑,它会问,你是否有 dispatch 和 action,如果是,它是一个 action 还是一个函数?

In the body of it, you see the logic of whats going on and it asks, did you dispatch and action and if so is it an action or a function?

我上面描述的所有内容都包含在源代码中.

Everything I described above is captured in the source code.

所以为了让我正确地将 Redux-Thunk 应用到我给你的例子中,我会转到我的根 index.js 文件并在将它安装到终端后像这样导入:

So for me to properly apply Redux-Thunk to the example I gave you I would go to my root index.js file and import after installing it in terminal like so:

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 的方式.

Notice I also imported the applyMiddleware. This function is how we connect a middleware to Redux.

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

So then I apply the createStore up front into a variable called store and implement that inside the Provider store like so:

const store = createStore(reducers);

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

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

To hook up Redux-Thunk, as a second argument I will call applyMiddleware and pass in thunk like so:

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

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

然后在我的动作创建器中进行一两个更改.我仍然可以返回一个带有 type 属性的普通对象,这是一个选项,使用 Redux-Thunk 我们仍然可以创建返回对象的普通动作创建器,但我们不必返回一个动作.

Then inside my action creator I make one or two changes. I can still return an normal object with a type property, that is an option, with Redux-Thunk we can still make normal action creators that return objects, but we don't have to return an action.

因此,我可以调用 dispatch 并像这样传入我的操作对象,而不是返回一个动作:

So rather than returning an action I can call dispatch and pass in my action object like so:

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 不会得到返回的引用并使用它,我们可以返回或不返回,我们关心的是我们从外部函数返回的内容.

With Redux-Thunk we can use async/await syntax, because this syntax is only going to modify the return value of the inner function. Nothing from the function will ever get used. Redux-Thunk will not get a reference of what gets returned and make use of it, we can return or not return, its what we return from our outer function is what we care about.

重构我刚刚在上面分享的内容的常用方法如下:

A common way of refactoring what I just shared above is like so:

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

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

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

So if you don't make use of getState within the function, you can leave it out as an argument. You can make your code even more concise like so:

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

您会在许多 Redux 项目中看到这一点.就是这样.

You will see this in a lot of Redux projects. Thats it.

这篇关于我如何解决“操作必须是普通对象.使用自定义中间件进行异步操作.]"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆