如何解决 "Actions must be plain objects. Use custom middleware for async actions.]"?

How do I resolve "Actions must be plain objects. Use custom middleware for async actions.]"?

所以我在这上面浪费了 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." }));
      }
    };

调用总是在 Actions must be plain objects. Use custom middleware for async actions.] 结束。

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

注意:fetchUserRequest()fetchUserSuccess()fetchUserFailure() 都是 return 简单、普通的 redux 操作。

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

需要

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

(getState, dispatch) !== (dispatch, getState)

理解这个错误消息是理解 Redux 世界中很多东西的关键。这甚至可能是你以后被问到的面试问题。

实际上,您的动作创建器有两个问题。你的动作创建者的第一件事是你的动作创建者应该 return 普通 JavaScript 带有 type 属性 和可选的 payload [=] 的对象166=] 也一样,但目前您还没有 return 从动作创建者那里执行动作。

您可能会看看您的代码编辑器和动作创建器,您可能会想,您正在看和我一样的动作创建器吗?看起来您可能 return 正在使用 type 属性 对象,但实际上您不是。

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

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

所以即使这个函数看起来像它 returning 一个带有 type 属性 的对象,事实上,在我们将它转​​换为 es2015 代码之后,我们不是。

下次将您的异步操作创建器放入 babeljs.io,您就会明白我的意思。

这实际上是将我们的代码转译为 ES2015。

所以在代码编辑器内部,你认为你正在执行你编写的代码,但实际上,因为你特别有这个 async/await 语法,所以整个功能被扩展到你在右边看到的babeljs.io.

的一侧

所以当我对你说你的动作创建者不是 returning 普通的 JavaScript 对象时,那是因为你有 async/await 语法。这就是您的动作创建器未按预期工作的原因。

所以你 return,而不是你最初调用时的动作对象。当您的动作创建者第一次被调用时,您没有 return 动作对象,相反,如您所见,您在 return 请求对象中有一些代码。这就是 returned 的内容——一个请求。您 return 来自您的动作创建者的请求并进入 store.dispatch 方法。

然后 redux store 会查看 returned 的内容并说好的,这是一个只有 type 属性 的普通 JavaScript 对象吗?好吧,在这种情况下,不,因为我们只是 return 编辑了请求对象,我们没有 return 我们的操作,这就是为什么我们最终看到令人讨厌的红色消息说操作必须是普通对象。所以我们没有 return 普通对象,动作必须 return 普通对象。我们 return 编辑了一个请求对象,它可能分配了一些奇特的方法,可能不是 type 属性,所以我们绝对没有发送我们认为正在发送的内容。

这都是因为您使用的 async/await 语法。

这是您的动作创作者的第 1 期。由于使用了被转译为 es5 代码的 async/await 语法,在浏览器中实际运行的并不是您认为实际运行的。

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

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

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

Redux-Thunk 是最流行的中间件,因为它可以帮助我们使用异步操作创建器。

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

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

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

一旦我们将 Redux-Thunk 包含在我们的 action creator 中,它就可以 return 普通对象或者它可以 return 函数。

你知道这是怎么回事吗?

那么 return 函数有什么帮助?

所以我们的动作创建者 return 是一个对象或函数形式的“动作”。该“动作”将被发送到调度函数,最终将在 Redux-Thunk 中结束。

Redux-Thunk 会说,“嗨,动作,你是一个函数还是一个对象?”如果“action”告诉 Redux-Thunk 它是一个对象,Redux-Thunk 会说,“好吧,谢谢你过来,action,但我更喜欢只处理函数”,然后 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 将 return 你商店内的所有数据。这两个参数在我们的 Redux 应用程序中拥有无限的力量。通过 dispatch 我们可以更改任何我们想要的数据,通过 getState 我们可以读取任何我们想要的数据。

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

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

在它的正文中,您会看到正在发生的事情的逻辑,它会询问您是否调度和操作,如果是,它是操作还是函数?

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

因此,为了让我正确地将 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")
    );

然后在我的 action creator 中进行一两处更改。我仍然可以 return 类型为 属性 的普通对象,这是一个选项,使用 Redux-Thunk 我们仍然可以创建 return 对象的普通动作创建者,但我们没有return 一个动作。

所以我可以调用 dispatch 并像这样传递我的操作对象,而不是 return 执行操作:

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

对于 Redux-Thunk 我们可以使用 async/await 语法,因为这个语法只会修改内部函数的 return 值。该函数的任何内容都不会被使用。 Redux-Thunk 不会获得对 returned 和使用它的引用,我们可以 return 或不 return,这是我们从外部函数 return 得到的才是我们关心的。

重构我刚刚在上面分享的内容的一种常见方法是这样的:

    export const fetchPosts = () => {
      return async (dispatch) => {
        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 项目中看到这一点。就是这样。