【问题标题】:How to use Redux-Thunk with Redux Toolkit's createSlice?如何将 Redux-Thunk 与 Redux Toolkit 的 createSlice 一起使用?
【发布时间】:2021-04-14 08:28:59
【问题描述】:

我遇到了 Redux Toolkit (RTK),并希望实现它提供的更多功能。我的应用程序分派到通过createSlice({}) 创建的reducers 切片(参见createSlice api docs

到目前为止,这非常有效。我可以轻松地使用内置的 dispatch(action)useSelector(selector) 来调度操作并在我的组件中很好地接收/响应状态变化。

我想使用来自 axios 的异步调用从 API 获取数据并更新存储,因为请求是 A) 开始 B) 完成。

我见过redux-thunk,它似乎完全是为此目的而设计的,但是在一般谷歌搜索之后,新的 RTK 似乎在createSlice() 中不支持它。

以上是目前用切片实现thunk的状态吗?

我在文档中看到您可以将 extraReducers 添加到切片中,但不确定这是否意味着我可以创建更多使用 thunk 的 传统 减速器并让切片实现它们?

总的来说,这是一种误导,因为 RTK 文档显示您可以使用 thunk,但似乎没有提到它不能通过新的 slices api 访问。

来自Redux Tool Kit Middleware的示例

const store = configureStore({
  reducer: rootReducer,
  middleware: [thunk, logger]
})

我的切片代码显示了异步调用将失败的位置以及其他一些可以工作的示例减速器。

import { getAxiosInstance } from '../../conf/index';

export const slice = createSlice({
    name: 'bundles',
    initialState: {
        bundles: [],
        selectedBundle: null,
        page: {
            page: 0,
            totalElements: 0,
            size: 20,
            totalPages: 0
        },
        myAsyncResponse: null
    },

    reducers: {
        //Update the state with the new bundles and the Spring Page object.
        recievedBundlesFromAPI: (state, bundles) => {
            console.log('Getting bundles...');
            const springPage = bundles.payload.pageable;
            state.bundles = bundles.payload.content;
            state.page = {
                page: springPage.pageNumber,
                size: springPage.pageSize,
                totalElements: bundles.payload.totalElements,
                totalPages: bundles.payload.totalPages
            };
        },

        //The Bundle selected by the user.
        setSelectedBundle: (state, bundle) => {
            console.log(`Selected ${bundle} `);
            state.selectedBundle = bundle;
        },

        //I WANT TO USE / DO AN ASYNC FUNCTION HERE...THIS FAILS.
        myAsyncInSlice: (state) => {
            getAxiosInstance()
                .get('/')
                .then((ok) => {
                    state.myAsyncResponse = ok.data;
                })
                .catch((err) => {
                    state.myAsyncResponse = 'ERROR';
                });
        }
    }
});

export const selectBundles = (state) => state.bundles.bundles;
export const selectedBundle = (state) => state.bundles.selectBundle;
export const selectPage = (state) => state.bundles.page;
export const { recievedBundlesFromAPI, setSelectedBundle, myAsyncInSlice } = slice.actions;
export default slice.reducer;

我的商店设置(商店配置)。

import { configureStore } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';

import bundlesReducer from '../slices/bundles-slice';
import servicesReducer from '../slices/services-slice';
import menuReducer from '../slices/menu-slice';
import mySliceReducer from '../slices/my-slice';

const store = configureStore({
    reducer: {
        bundles: bundlesReducer,
        services: servicesReducer,
        menu: menuReducer,
        redirect: mySliceReducer
    }
});
export default store;

【问题讨论】:

    标签: javascript redux redux-thunk redux-toolkit


    【解决方案1】:

    我是 Redux 维护者和 Redux Toolkit 的创建者。

    FWIW,使用 Redux Toolkit 更改 Redux 进行异步调用无关紧要。

    您仍将使用异步中间件(通常为 redux-thunk),获取数据,并根据结果调度操作。

    从 Redux Toolkit 1.3 开始,我们确实有一个名为 createAsyncThunk 的辅助方法,它可以生成动作创建者并为您请求生命周期动作分派,但它仍然是相同的标准过程。

    文档中的这个示例代码总结了用法;

    import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
    import { userAPI } from './userAPI'
    
    // First, create the thunk
    const fetchUserById = createAsyncThunk(
      'users/fetchByIdStatus',
      async (userId, thunkAPI) => {
        const response = await userAPI.fetchById(userId)
        return response.data
      }
    )
    
    // Then, handle actions in your reducers:
    const usersSlice = createSlice({
      name: 'users',
      initialState: { entities: [], loading: 'idle' },
      reducers: {
        // standard reducer logic, with auto-generated action types per reducer
      },
      extraReducers: (builder) => {
        // Add reducers for additional action types here, and handle loading state as needed
        builder.addCase(fetchUserById.fulfilled, (state, action) => {
          // Add user to the state array
          state.entities.push(action.payload)
        })
      },
    })
    
    // Later, dispatch the thunk as needed in the app
    dispatch(fetchUserById(123))
    

    有关此主题的更多信息,请参阅 the Redux Toolkit "Usage Guide: Async Logic and Data Fetching" docs page

    希望这会为您指明正确的方向!

    【讨论】:

    • 谢谢。我看到你评论了一个 Git 问题,但由于它仍然开放,我认为还没有实现(因此没有进一步冒险)。顺便说一句,伟大的工作,不能等待下一个主要版本!我主要是一名 Spring Boot 后端开发人员,整个前端世界的新手让我头晕目眩,但你的工作使它变得非常易于理解和使用。可能应该看一下“高级”,但已经做了几天的 redux(以及几个小时的 RTK)我并没有推到那么远哈!
    • 太好了,感谢您的积极反馈!是的,RTK 文档目前是在假设您已经知道 Redux 的工作原理的情况下编写的,因此他们专注于解释使用 RTK 与“手动”编写 Redux 逻辑有何不同。 RTK 1.3.0 之后我的下一个任务是添加一个新的 Redux 核心文档“快速入门”页面,假设您是 Redux 新手,并展示了从头开始使用 RTK 编写 Redux 代码的最简单方法。
    • 那将是完美的。我发现自己潜入了前端世界......并在 2 周的时间里经历了以下方面的演变: 1. ReactJS 仅具有生命周期和状态。 2.具有生命周期和redux和MaterialUI。 3.带钩子和非钩子的redux。 4. 使用 redux-hooks。 5. 使用 RTK。 6.(今天)使用 thunk 并使用 createAsyncThunk() 尝试您相当棒的 1.3.0 alpha。 “我该怎么做才能开始使用 redux 来帮助新手”将是另一个很棒的补充恕我直言。再次感谢您的伟大项目!
    • 是的,这是一个很多的投入!我们通常建议人们真正应该只在他们已经习惯使用 React 时才处理 Redux。这样一来,一次性学习的新概念就更少了,而且 Redux 如何适合 React 应用程序以及它为什么有用也更加明显。但是,是的,很高兴 RTK 被证明是有帮助的!密切关注 Redux 文档 - 我希望在接下来的几周内开始整理“快速入门”页面。
    • @markerison 在这种情况下,您将如何监控功能组件的卸载?我知道你可以在调度调用中使用 useEffect() 和 abort() 方法来做到这一点,但是如果调度是在按钮上单击调度(fetchUserById(123)),那么你会在哪里检查卸载?
    【解决方案2】:

    您可以使用createAsyncThunk创建thunk action,可以使用dispatch触发

    teamSlice.ts

    import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
    const axios = require("axios");
    
    export const fetchPlayerList = createAsyncThunk(
      "team/playerListLoading",
      (teamId: string) =>
        axios
          .get(`https://api.opendota.com/api/teams/${teamId}/players`)
          .then((response) => response.data)
          .catch((error) => error)
    );
    
    const teamInitialState = {
      playerList: {
        status: "idle",
        data: {},
        error: {},
      },
    };
    
    const teamSlice = createSlice({
      name: "user",
      initialState: teamInitialState,
      reducers: {},
      extraReducers: {
        [fetchPlayerList.pending.type]: (state, action) => {
          state.playerList = {
            status: "loading",
            data: {},
            error: {},
          };
        },
        [fetchPlayerList.fulfilled.type]: (state, action) => {
          state.playerList = {
            status: "idle",
            data: action.payload,
            error: {},
          };
        },
        [fetchPlayerList.rejected.type]: (state, action) => {
          state.playerList = {
            status: "idle",
            data: {},
            error: action.payload,
          };
        },
      },
    });
    
    export default teamSlice;
    

    Team.tsx 组件

    import React from "react";
    import { useSelector, useDispatch } from "react-redux";
    
    import { fetchPlayerList } from "./teamSlice";
    
    const Team = (props) => {
      const dispatch = useDispatch();
      const playerList = useSelector((state: any) => state.team.playerList);
    
      return (
        <div>
          <button
            onClick={() => {
              dispatch(fetchPlayerList("1838315"));
            }}
          >
            Fetch Team players
          </button>
    
          <p>API status {playerList.status}</p>
          <div>
            {playerList.status !== "loading" &&
              playerList.data.length &&
              playerList.data.map((player) => (
                <div style={{ display: "flex" }}>
                  <p>Name: {player.name}</p>
                  <p>Games Played: {player.games_played}</p>
                </div>
              ))}
          </div>
        </div>
      );
    };
    
    export default Team;
    

    【讨论】:

    • 你为什么要放 [fetchPlayerList.fulfilled.type] 即 .type extra?
    • @AkshayVijayJain 对于打字稿,我已经添加了它。如果您仅使用 es6,则可以使用 [fetchPlayerList.fulfilled]
    • @AkshayVijayJain 兄弟,你救了我的命。我只发现了 builder 奇怪的语法,但这也有效。只需在最后添加这些.type。谢谢!
    • RTK 网站上缺少关于如何从 createAsyncThunk 获取动作名称的说明,感谢您指出这一点
    • @sevenlops 我知道这有点晚了,但请注意“奇怪的构建器语法”是我们推荐的写作方式。由于 TypeScript 和 IDE 支持不佳,我们目前不推荐使用对象表示法。
    【解决方案3】:

    使用redux-toolkit v1.3.0-alpha.8

    试试这个

    import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
    
    export const myAsyncInSlice = createAsyncThunk('bundles/myAsyncInSlice', () =>
      getAxiosInstance()
        .get('/')
        .then(ok => ok.data)
        .catch(err => err),
    );
    
    const usersSlice = createSlice({
      name: 'bundles',
      initialState: {
        bundles: [],
        selectedBundle: null,
        page: {
          page: 0,
          totalElements: 0,
          size: 20,
          totalPages: 0,
        },
        myAsyncResponse: null,
        myAsyncResponseError: null,
      },
      reducers: {
        // add your non-async reducers here
      },
      extraReducers: {
        // you can mutate state directly, since it is using immer behind the scenes
        [myAsyncInSlice.fulfilled]: (state, action) => {
          state.myAsyncResponse = action.payload;
        },
        [myAsyncInSlice.rejected]: (state, action) => {
          state.myAsyncResponseError = action.payload;
        },
      },
    });
    
    
    

    【讨论】:

      猜你喜欢
      • 2021-05-22
      • 2018-07-08
      • 1970-01-01
      • 2018-11-09
      • 2022-10-14
      • 2016-08-07
      • 2017-07-29
      相关资源
      最近更新 更多