如何使用 Redux-Thunk 和 Redux Toolkit 的 createSlice?

How to use Redux-Thunk with Redux Toolkit's createSlice?

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

到目前为止效果很好。我可以轻松地使用内置的 dispatch(action)useSelector(selector) 来分派动作,并且 receive/react 可以在我的组件中很好地改变状态。

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

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

上面是用切片实现thunk的现状吗?

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

总的来说,它具有误导性,因为 RTK 文档显示您可以使用 thunk,但似乎没有提到它不能通过新切片访问 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;

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


我是 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

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

您可以使用 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;