首页 > 解决方案 > 我想为后端的任何请求(GET、POST、PUT、DELETE)构建一个 React - Typescript 自定义钩子

问题描述

我正在构建一个全栈练习应用程序,我的一些对象包括例程、练习、练习集等。我没有为每个资源写出特定的获取、发布、放置和删除请求,而是想知道是否有更动态的方法来使用自定义 React 钩子发出请求。我见过很多 useFetch 钩子的例子,但对于其他请求类型来说并不多。我已经构建了一个自定义钩子,并创建了一个在我的组件中发出请求的策略。这似乎可行,但如果他们愿意,或者如果他们可以展示一些我正在努力实现的目标,我希望从一些更有经验的开发人员那里获得一些反馈。谢谢!

类型定义

import { AxiosError, AxiosResponse } from 'axios'
import React from 'react'

export type StatusType = "fetching" | "creating" | "updating" | "deleting" | "done" 

export interface DataReturnType {
  response: AxiosResponse | undefined
  status: StatusType
  error: AxiosError | undefined
}

// Every time there's a new resource added to my back end, all I have to do is add a 
// new endpoint string here and I automatically get all the requests.

export type ResourceEndpointType = "routines" | "routines/routine" | "routines/weeks" | "set-groups" | "exercise-sets" | "exercises" | "users"

// Below essentially tells you how the request is built every time you need it. 
// TypeScript makes it easy to implement because you never have to remember what the endpoint is,
// it just pops up as you're typing. Furthermore, this approach imposes  more consistent structure to requests. 

export type RequestObjType = {
  resourceEndPoint: ResourceEndpointType
  parameter?: string
  query?: string
  request: "get" | "post" | "put" | "delete"
  body?: object
  trigger:boolean
  setTrigger: React.Dispatch<React.SetStateAction<boolean>>
}

钩子

import { useEffect, useState } from "react";
import { AxiosError, AxiosResponse } from "axios";
import axiosWithAuth from "../utils/axiosWithAuth"; // an instance of axios with baseUrl and appropriate headers
import { DataReturnType, RequestObjType, StatusType } from "./apiTypes";

enum Statuses {
  get = "fetching",
  post = "creating",
  put = "updating",
  delete = "deleting",
  done = "done",
}

const useRequest = (requestObj: RequestObjType): DataReturnType => {
  const [response, responseSet] = useState<AxiosResponse | undefined>(
    undefined
  );
  const [status, statusSet] = useState<StatusType>(Statuses.done);
  const [error, errorSet] = useState<AxiosError | undefined>(undefined);

  let url = requestObj.resourceEndPoint;
  if (requestObj.parameter) url += "/" + requestObj.parameter;
  if (requestObj.query) url += "?" + requestObj.query;

  useEffect(() => {
    if (requestObj.trigger) { // trigger and setTrigger are passed in by the component
      console.log(`Requesting ${requestObj.request.toUpperCase()}...`);
      statusSet(Statuses[requestObj.request]);
      axiosWithAuth()
        [requestObj.request](url, requestObj.body)
        .then((response: AxiosResponse) => {
          requestObj.setTrigger(false);
          responseSet(response);
          statusSet("done");
        })
        .catch((error: AxiosError) => {
          console.log(error);
          errorSet(error);
          statusSet("done");
          requestObj.setTrigger(false);
        });
    }
    // resetting response and error every time will allow for conditional checks in the component
    responseSet(undefined);
    errorSet(undefined);
    requestObj.setTrigger(false);

  }, [url, requestObj]);

  return {
    response,
    status,
    error,
  };
};

export default useRequest;

在搜索栏组件中使用 GET 钩子

  const [search, setSearch] = useState("");
  const { loadExercises, paginationSet } = useExerciseContext(); // custom hook built from useContext
  const [trigger, shouldFetch] = useState(false);

  const { response, status } = useRequest({
    request: "get",
    query: `name=${search}`,
    resourceEndPoint: "exercises",
    trigger,
    setTrigger: shouldFetch,
  });
  // watching for changes in the response
  useEffect(() => {
    if (response) {
      loadExercises(response.data.data);
      paginationSet(response.data.pagination)
    }
  }, [loadExercises, response, status, paginationSet]);

 ... 
     <Button
        onClick={() => shouldFetch(true)} // triggering the request
        className={classes.submitBtn}
        variant="outlined"
      >
        Submit
      </Button>

在另一个组件中使用 PUT 挂钩

  const { changeRoutineColor, routineColorMap, replaceRoutine } =
    useRoutinesContext();
  const routineColor = routineColorMap[routine._id];
  const classes = useItemStyles({ routineColor });
  const [shouldUpdate, setShouldUpdate] = useState(false); // you can make the triggers more semantic
  const { response, status, error } = useUpdate({ // being more semantic with the useResource import
    resourceEndPoint: "routines/routine",
    parameter: routine._id,
    query: "select=color", // I don't need the entire object back from the backend (MongoDB/Node/Mongoose) so I can add a select to the query
    request: "put",
    trigger: shouldUpdate,
    setTrigger: setShouldUpdate,
    body: { color: routineColor },
  });
  
  // handling the response
  useEffect(() => {
    if (response && response.data) {
      replaceRoutine({ ...routine, color: response.data.data.color });
    }
  }, [replaceRoutine, response, routine, error]);

  const onChange = (newColor: string) => {
    changeRoutineColor(routine._id, newColor);
  };
  // 
  const handleClose = () => {
    popupState.close();
    setShouldUpdate(true); // triggering the request
  };


标签: reactjstypescriptaxios

解决方案


推荐阅读