简体   繁体   English

如何将 Redux-Thunk 与 Redux Toolkit 的 createSlice 一起使用?

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

I have come across Redux Toolkit (RTK) and wanting to implement further functionality it provides.我遇到了 Redux Toolkit (RTK),并希望实现它提供的更多功能。 My application dispatches to reducers slices created via the createSlice({}) (see createSlice api docs )我的应用程序分派给通过createSlice({})创建的 reducers 切片(请参阅createSlice api docs

This so far works brilliantly.到目前为止,这非常有效。 I can easily use the built in dispatch(action) and useSelector(selector) to dispatch the actions and receive/react to the state changes well in my components.我可以轻松地使用内置的dispatch(action)useSelector(selector)来分派动作并很好地接收/响应组件中的状态变化。

I would like to use an async call from axios to fetch data from the API and update the store as the request is A) started B) completed.我想使用来自 axios 的异步调用从 API 获取数据并更新存储,因为请求是 A) 开始 B) 完成。

I have seen redux-thunk and it seems as though it is designed entirely for this purpose, but the new RTK does not seem to support it within a createSlice() following general googling.我见过redux-thunk ,它似乎完全是为此目的而设计的,但新的 RTK 似乎在一般谷歌搜索之后的createSlice()中不支持它。

Is the above the current state of implementing thunk with slices?以上是使用切片实现 thunk 的当前状态吗?

I have seen in the docs that you can add extraReducers to the slice but unsure if this means I could create more traditional reducers that use thunk and have the slice implement them?我在文档中看到您可以将extraReducers添加到切片,但不确定这是否意味着我可以创建更多使用 thunk 的传统减速器并让切片实现它们?

Overall, it is misleading as the RTK docs show you can use thunk, but doesn't seem to mention it not being accessible via the new slices api.总的来说,这是一种误导,因为 RTK 文档显示您可以使用 thunk,但似乎没有提到它不能通过新的 slices api 访问。

Example from Redux Tool Kit Middleware Redux 工具包中间件示例

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

My code for a slice showing where an async call would fail and some other example reducers that do work.我的切片代码显示了异步调用将失败的位置以及其他一些可以工作的示例减速器。

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;

My store setup (store config).我的商店设置(商店配置)。

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;

I'm a Redux maintainer and creator of Redux Toolkit.我是 Redux 维护者和 Redux Toolkit 的创建者。

FWIW, nothing about making async calls with Redux changes with Redux Toolkit. FWIW,与使用 Redux Toolkit 更改 Redux 进行异步调用无关。

You'd still use an async middleware (typically redux-thunk ), fetch data, and dispatch actions with the results.您仍然会使用异步中间件(通常是redux-thunk )、获取数据并根据结果分派操作。

As of Redux Toolkit 1.3, we do have a helper method called createAsyncThunk that generates the action creators and does request lifecycle action dispatching for you, but it's still the same standard process.从 Redux Toolkit 1.3 开始,我们确实有一个名为createAsyncThunk的辅助方法,它生成动作创建者并为您请求生命周期动作分派,但它仍然是相同的标准过程。

This sample code from the docs sums up the usage;文档中的示例代码总结了用法;

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))

See the Redux Toolkit "Usage Guide: Async Logic and Data Fetching" docs page for some additional info on this topic.有关此主题的一些其他信息,请参阅Redux Toolkit“使用指南:异步逻辑和数据获取”文档页面

Hopefully that points you in the right direction!希望这为您指明了正确的方向!

You can use createAsyncThunk to create thunk action , which can be trigger using dispatch您可以使用createAsyncThunk来创建thunk action ,可以使用dispatch触发

teamSlice.ts 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 component 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;

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

Try this尝试这个

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


Create Slice PostSlice.js创建切片 PostSlice.js

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";


export const getPosts = createAsyncThunk(
    'posts/getPosts',
    async ()=>{
        const res = await fetch('https://jsonplaceholder.typicode.com/posts').then(
            (data) => data.json())
        return res;
    }
)

const initialState = {
    entities: [],
    loading: false
}

export const postSlice = createSlice({
    name: 'posts',
    initialState,
    reducers:{},
    extraReducers:{
        [getPosts.pending]: (state)=>{
            state.loading='loading'
        },
        [getPosts.fulfilled]: (state, action)=>{
            state.loading='fullfilled'
            state.entries=action.payload
        },
        [getPosts.rejected]: (state, action)=>{
            state.loading='rejected'
            state.error=action.error.message
        }
    }
})


export const postReducer = postSlice.reducer;

Create component App.js:创建组件 App.js:

import { useDispatch, useSelector } from 'react-redux'
import { getPosts } from "./PostSlice";
import {useEffect} from "react";
import { unwrapResult } from "@reduxjs/toolkit";

function App() {
  const dispatch = useDispatch();
  const {entries, loading, error} = useSelector((state) => state.posts);
  
  
  useEffect(() => {
    dispatch(getPosts()).then(unwrapResult).then(obj=> console.log(obj, 'obj')).catch(obj=> console.log(obj, 'error'))
  }, [])

 if(loading==='loading'){
  return <p>loading</p>
 }  else if(loading === 'rejected') {
   return <p>{error}</p>
 }
 return <p>fullfilled</p>
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM