关于javascript:如何在Redux Toolkit的createSlice中使用Redux-Thunk?

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

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

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

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

RTK确实通过使用thunk中间件在减速器中支持thunk(请参阅答案)。

在1.3.0版本(当前是2020年2月的Alpha )中,有一个辅助方法createAsyncThunk() createAsyncThunk将提供一些有用的功能(即根据状态触发3个"扩展的"减速器)的Promise)。

ReduxJS / Toolkit NPM版本

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

我对Redux还是很陌生,遇到过Redux Toolkit(RTK),并想实现它提供的其他功能(或者在这种情况下可能不?)(2020年2月)

我的应用程序调度到通过createSlice({})创建的reducers切片(请参见createSlice api文档)

到目前为止,它的工作非常出色。我可以轻松地使用内置的dispatch(action)useSelector(selector)来分派操作并很好地接收/响应组件中的状态更改。

我想使用axios的异步调用从API提取数据并更新存储,因为请求是A)启动B)完成。

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

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

我在文档中看到您可以向slice中添加extraReducer,但是不确定这是否意味着我可以创建更多使用thunk的传统化简器,并让slice实现它们?

总体而言,这是一种误导,因为RTK文档显示您可以使用thunk ...但是似乎没有提及它无法通过新的slice API进行访问。

来自Redux Tool Kit中间件的示例

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

我的代码片段显示异步调用将在何处失败以及其他一些可以运行的示例化约器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的维护者和Redux Toolkit的创建者。

FWIW,与通过Redux Toolkit进行Redux更改进行异步调用无关。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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))

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

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


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

触发

teamSlice.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 (
   
      <button
        onClick={() => { dispatch(fetchPlayerList('1838315')); }}
      >Fetch Team players</button>

      <p>API status {playerList.status}</p>
     
        { (playerList.status !== 'loading' && playerList.data.length) &&
          playerList.data.map((player) =>
           
              <p>Name: {player.name}</p>
              <p>Games Played: {player.games_played}</p>
           
          )
        }
     
   
  )
}

export default Team;


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

试试这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
    },
  },
});