如何在 Redux Toolkit 的 createSlice 中使用 Redux-Thunk?

use*_*188 29 redux redux-thunk redux-toolkit

==================== TLDR ==========================

@markerikson(请参阅已接受的答案)亲切地指出了当前的解决方案和未来的解决方案。

编辑:2020 年 11 月 15 日:链接到文档以在 Slice 中使用 Async Thunk

RTK确实支持使用 thunk 中间件的 reducer 中的 thunk(参见答案)。

在 1.3.0 版本(目前 alpha 于 2020 年 2 月)中,有一个辅助方法createAsyncThunk() createAsyncThunk将提供一些有用的功能(即根据 Promise的状态触发 3 个“扩展”reducer)。

ReduxJS/Toolkit NPM 发布

========================原始帖子 2020 年 2 月====================== ====

我对 Redux 很陌生,遇到了 Redux Toolkit (RTK) 并希望实现它提供的更多功能(或者在这种情况下可能没有?)(2020 年 2 月)

我的应用程序分派到通过创建的减速器切片createSlice({})(请参阅createSlice api docs

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

我想使用 axios 的异步调用从 API 获取数据并在请求 A) 开始 B) 完成时更新存储。

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

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

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

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

Redux 工具包中间件示例

const store = configureStore({
  reducer: rootReducer,
  middleware: [thunk, logger]
})
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

任何帮助或进一步的指导将不胜感激。

mar*_*son 64

我是 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: {
    // Add reducers for additional action types here, and handle loading state as needed
    [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))
Run Code Online (Sandbox Code Playgroud)

有关此主题的一些其他信息,请参阅Redux Toolkit“使用指南:异步逻辑和数据获取”文档页面

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


Nik*_*rao 10

您可以使用createAsyncThunkto create 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;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

  • @AkshayVijayJain 对于打字稿,我已经添加了它。如果您仅使用 es6 那么您可以使用 [fetchPlayerList.fulfilled] (2认同)
  • @AkshayVijayJain 你救了我的命,兄弟。我只发现了“builder”奇怪的语法,但这也有效。只需在末尾添加这些“.type”即可。谢谢你! (2认同)

kar*_*pez 7

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;
    },
  },
});


Run Code Online (Sandbox Code Playgroud)