redux 如何键入CreateAsyncThunk响应数据以正确存储和处理状态?

zbq4xfa0  于 2023-02-12  发布在  其他
关注(0)|答案(1)|浏览(138)

在写项目的时候,Calculator遇到了这样的问题,给我的任务是把项目分成模块,如果需要的话,可以用其他的模块来替换,我们以API为例,API代码应该写得直观明了,怎么用其他的模块来替换。
因此,我决定键入负责API请求的函数。也许我做错了,如果你指出错误就太好了。

//./api/types.ts

export type HistoryItem = { //This is an interface of item for request, but not respond
  expression: string;
  result: string;
};

export type CalculationRequest<T> = (expression: string) => T[];
export type DeleteRequest = (id: string) => void;
export type GetAllRequest<T> = () => T[];

创建泛型的想法是API可以接受某种类型的数据作为输入并返回某种类型的数据,但在将该数据传递给状态之前,它必须是强类型的,以匹配状态的类型。
接下来,我编写了一个useApi钩子,它只检查.env,选择所需的API,并将fetch函数传递给切片。

import { defaultApi } from "@/api";
import {
  CalculationRequest,
  DeleteRequest,
  GetAllRequest,
  HistoryItem,
} from "@/api/types";

export type ApiMethods = {
  calculateExpression: CalculationRequest<HistoryItem>;
  deleteHistoryItem: DeleteRequest;
  fetchHistory: GetAllRequest<HistoryItem>;
};

type UseApiHook = () => ApiMethods;

export const useApi: UseApiHook = () => {
  if (process.env.NEXT_PUBLIC_REACT_APP_API === "default") {
    return defaultApi;
  } else {
    throw new Error("API was not found!");
  }
};

在切片中,我将获取函数 Package 在createAsyncThunk中并编写它们的功能。

const fetchHistory = createAsyncThunk(
  "history/get",
  api.fetchHistory
);
const deleteHistoryItem = createAsyncThunk(
  "history/delete",
  api.deleteHistoryItem
);
const calculateExpression = createAsyncThunk(
  "calculator/get",
  api.calculateExpression
);

const maxNumberOfHistoryItems = 10;

const initialState: CalculatorHistoryState = {
  history: [],
  inputValue: "0",
  fetchError: null,
  status: "idle",
};

const calculatorHistorySlice = createSlice({
  name: "history",
  initialState,
  reducers: {
    addItemToState(state, action) {
      const updatedHistory = [action.payload, ...state.history];
      if (updatedHistory.length < maxNumberOfHistoryItems) {
        return { ...state, history: updatedHistory };
      }
      return {
        ...state,
        history: updatedHistory.slice(0, maxNumberOfHistoryItems),
      };
    },

    removeFromState(state, action) {
      const filteredHistory = state.history.filter((item) => {
        return item._id != action.payload;
      });
      return { ...state, history: filteredHistory };
    },

    setItemToInput(state, action) {
      return { ...state, inputValue: action.payload };
    },
  },
  extraReducers(builder) {
    builder.addCase(HYDRATE, (state, _action) => {
      return {
        ...state,
        // ...action.payload.subject,
      };
    });

    builder.addCase(fetchHistory.fulfilled, (state, { payload }) => {
      state.history = [...payload];
      state.status = "idle";
    });

    builder.addCase(calculateExpression.fulfilled, (state, _action) => {
      return state;
    });

    builder.addCase(deleteHistoryItem.fulfilled, (state, action) => {
      state.history.filter((item) => item._id != action.payload);
    });

    builder.addCase(deleteHistoryItem.rejected, (state, action) => {
      console.log(action);
      return state;
    });
  },
});

问题是我想把来自createAsyncThunk请求的数据进行类型化,比如更换API时,返回的数据类型可能会发生变化,但在进入状态之前,必须按照国家标准对数据进行格式化,这样以后才不会出错,直观上也容易理解。

export interface StateItem {
  expression: string;
  result: string;
  _id: string;
  __v?: string;
}

在代码中的什么地方应该为从API返回的状态指定数据类型?

igsr9ssn

igsr9ssn1#

这里的基本问题似乎是在哪里使用HistoryItem类型,而在哪里使用StateItem类型,StateItem类型通过添加_id来扩展HistoryItem
您可以通过查看如何使用变量以及出现哪些TypeScript错误来判断哪种类型是合适的。
我复制并粘贴了您的代码into the TypeScript playground,并创建了一个类型为ApiMethods的伪api变量。我看到的重要错误如下:

builder.addCase(fetchHistory.fulfilled, (state, { payload }) => {
--->  state.history = [...payload];
      state.status = "idle";
    });

类型“HistoryItem[]"不能分配给类型”WritableDraft[]“。
类型“HistoryItem”中缺少属性“_id”,但类型“WritableDraft”中需要该属性。
您的Reducer在许多地方使用了_id,因此我们知道我们需要state.historyStateItem[]而不是HistoryItem[]。此错误告诉我们,我们还需要您的fetchHistory thunk才能使用StateItem[]类型。您的代码依赖于fetchHistory API调用返回具有_id属性的项数组的预期(如果这是一个不正确的假设,那么你需要做其他的改变)。
您需要更改ApiMethods的类型。可能如下所示:

export type ApiMethods = {
  calculateExpression: CalculationRequest<StateItem>;
  deleteHistoryItem: DeleteRequest;
  fetchHistory: GetAllRequest<StateItem>;
};

但可能是这样的,如果你想说一个泛型API总是添加一个_id

interface ApiAdded {
    _id: string;
    __v?: string;
}

export type CalculationRequest<T> = (expression: string) => (T & ApiAdded)[];
export type DeleteRequest = (id: string) => void;
export type GetAllRequest<T> = () => (T & ApiAdded)[];

export type ApiMethods = {
    calculateExpression: CalculationRequest<HistoryItem>;
    deleteHistoryItem: DeleteRequest;
    fetchHistory: GetAllRequest<HistoryItem>;
};

你的deleteHistoryItem.fulfilled大小写还原器也有问题,因为你的action没有payload,如果DeleteRequest返回的void是准确的,你需要从参数中获取被删除项的id,而不是payload

builder.addCase(deleteHistoryItem.fulfilled, (state, action) => {
    state.history.filter((item) => item._id != action.meta.arg);
});

相关问题