Как получить условные типы возврата в ответном запросе

У меня есть общий метод использования useQuery или useMutation на основе метода HTTP в качестве опоры. Но тип возвращаемого значения этого метода содержит «QueryObserverRefetchErrorResult<any, Error>», который не имеет методов, включенных в useMutation или useQuery.

Я пробовал условные типы, такие как, но проблема была той же.

type ResultType<Method extends NTWRK_METHODS> = Method extends NTWRK_METHODS.GET ? NetworkQueryResult : NetworkMutationResult;

Мой метод


type NetworkQueryResult = UseQueryResult<any, Error>;
type NetworkMutationResult = UseMutationResult<any, Error, void, unknown>;

export function useNetworkQuery(
  props: IUseNetworkCallWithClient,
): NetworkQueryResult | NetworkMutationResult {
  const {queryKey, axiosClient, url, persist, method, params = ''} = props;

  if (method === NTWRK_METHODS.GET) {
    return useQuery({
      queryKey: [queryKey],
      queryFn: async () => {
        const data = await axiosClient.request({
          method: NTWRK_METHODS.GET,
          url: url,
          params: params,
        });
        return data.data;
      },
      enabled: false,
      meta: {persist},
    });
  } else {
    return useMutation({
      mutationKey: [queryKey],
      mutationFn: async () => {
        const data = await axiosClient.request({
          method: method,
          url: url,
          data: params,
        });
        return data.data;
      },
      meta: {persist},
    });
  }
} 

Я называю это так


export const makeNetworkCall = (props: IUseNetworkCallWithoutClient) => {
  const {method, queryKey, url, persist, params = ''} = props;
  const config = {
    axiosClient,
    queryKey: queryKey,
    method: method,
    url: url,
    persist: persist,
    params: params,
  };

  return useNetworkQuery(config);
};

а потом

export const useAuthCalls = () => {
  const logIn = () => {
    makeNetworkCall({
      method: NTWRK_METHODS.POST,
      url: '/users',
      queryKey: 'login',
      persist: true,
      params: '',
    });
  };

  return {logIn};
};

Быстрые вопросы. Является ли NTWRK_METHODS перечислением ? Последние два фрагмента кода абсолютно одинаковы, было ли это ошибкой? Являются ли makeNetworkCall и useNetworkQuery одинаковыми функциями или разными?

moonstar-x 13.06.2024 22:28

да, это перечисление, и повторяющийся код был ошибкой, отредактирован и исправлен.

Keshav Khetan 14.06.2024 13:09

Небольшое примечание: как я вижу, как вы определяете эти функции, особенно makeNetworkCall (у которого нет префикса use, но все же является ловушкой), у вас может возникнуть соблазн вызвать их в обработчиках действий (например, onClick кнопки) . Имейте в виду, что они по-прежнему являются хуками и должны всегда вызываться в одном и том же порядке при рендеринге в компоненте React. Если вы попытаетесь вызвать свой метод logIn, например, при нажатии кнопки, вы, вероятно, получите предупреждение, если не настоящую ошибку.

moonstar-x 14.06.2024 17:24

Вы правы, и спасибо, что поправили меня, я действительно получил предупреждение. В том же порядке вы имеете в виду, что я должен вызвать метод входа в систему, затем makeNetworkCall, а затем usenetworkquery?

Keshav Khetan 14.06.2024 17:55

Нет, тот же порядок подразумевает, что хуки следует вызывать всегда при рендеринге компонента. Например, вы не должны условно вызывать хуки. Вызов перехватчика из обработчика действий по определению условен, поскольку он будет вызываться только при нажатии кнопки. Ваша текущая реализация имеет ошибочный дизайн, потому что, чтобы избежать этого предупреждения, вам придется вызывать login каждый раз, когда ваш компонент повторно визуализируется.

moonstar-x 14.06.2024 17:58

Более того, useMutation фактически не запускает запрос при вызове, он возвращает функции mutate и mutateAsync, которые можно использовать для фактического запуска запроса. Это те, которые вы должны использовать в своих обработчиках действий, а не сами useMutation.

moonstar-x 14.06.2024 18:01

Понятно, мне следует запустить перехватчик при рендеринге и, возможно, использовать mutate как многоразовый метод. Совсем забыл правило не вызывать хук условно.

Keshav Khetan 14.06.2024 18:07
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
7
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Несмотря на то, что мы обсуждали ранее в комментариях, я дам вам ответ относительно ввода вашей функции useNetworkQuery, и это примерно то, о чем вы спрашиваете в этом посте.


Что вам здесь нужно, так это перегрузка функции, чтобы помочь различить, что возвращает useNetworkQuery.

Простое определение типа возвращаемого значения вашей функции как NetworkQueryResult | NetworkMutationResult не будет работать достаточно хорошо, потому что TypeScript не сможет определить, какой из этих двух является возвращаемым, а только то, что все возвращаемое может быть одним из этих «поэтому относитесь к ним так, как будто они были оба».

Как насчет замены useNetworkQuery на это?

export function useNetworkQuery<TData = any, TError extends Error = Error>(props: Exclude<IUseNetworkCallWithClient, "method"> & { method: NTWRK_METHODS.GET; }): UseQueryResult<TData, TError>;
export function useNetworkQuery<TData = any, TError extends Error = Error, TVariables = any>(props: Exclude<IUseNetworkCallWithClient, "method"> & { method: Exclude<NTWRK_METHODS, NTWRK_METHODS.GET>; }): UseMutationResult<TData, TError, TVariables>;
export function useNetworkQuery<TData = any, TError extends Error = Error, TVariables = any>(props: IUseNetworkCallWithClient): UseQueryResult<TData, TError> | UseMutationResult<TData, TError, TVariables> {
  const { queryKey, axiosClient, url, persist, method, params = "" } = props;

  if (method === NTWRK_METHODS.GET) {
    return useQuery<TData, TError>({
      queryKey: [queryKey],
      queryFn: async () => {
        const data = await axiosClient.request({
          method: NTWRK_METHODS.GET,
          url: url,
          params: params,
        });
        return data.data;
      },
      enabled: false,
      meta: { persist },
    });
  }

  return useMutation<TData, TError, TVariables>({
    mutationKey: [queryKey],
    mutationFn: async () => {
      const data = await axiosClient.request({
        method: method,
        url: url,
        data: params,
      });
      return data.data;
    },
    meta: { persist },
  });
}

Небольшое пояснение по этому поводу:

  1. Мы перегружаем функцию двумя сигнатурами, которые меняют тип возвращаемого значения на UseQueryResult<TData, TError>, если данное props включает свойство method, равное NTWRK_METHODS.GET, или меняет его на UseMutationResult<TData, TError, TVariables> для любого другого NTWRK_METHODS, которое не является NTWRK_METHODS.GET.
  2. Мы добавляем в функцию дженерики для TData, TError и TVariables, которые являются дженериками, принимаемыми функциями useQuery и useMutation. Таким образом, ваша функция useNetworkQuery сможет сохранять правильное поведение при наборе текста вместо того, чтобы все устанавливать на any.

Предыдущая функция должна работать достаточно хорошо, а как насчет makeNetworkCall? Если вы попытаетесь вернуть useNetworkQuery напрямую, вы заметите, что получите сообщение об ошибке, сообщающее, что сигнатура вызова реализации не видна извне. Это связано с тем, что когда вы перегружаете функцию, при вызове функции учитываются только сигнатуры перегрузки, а сигнатуры реализации служат только для реализации.

Затем вам следует добавить дополнительную перегрузку к useNetworkQuery:

export function useNetworkQuery<TData = any, TError extends Error = Error, TVariables = any>(props: IUseNetworkCallWithClient): UseQueryResult<TData, TError> | UseMutationResult<TData, TError, TVariables>;

По сути, это та же подпись, что и реализация.

Теперь вы можете перегрузить свою функцию makeNetworkCall:

export function makeNetworkCall<TData = any, TError extends Error = Error>(props: Exclude<IUseNetworkCallWithClient, "method"> & { method: NTWRK_METHODS.GET; }): UseQueryResult<TData, TError>;
export function makeNetworkCall<TData = any, TError extends Error = Error, TVariables = any>(props: Exclude<IUseNetworkCallWithClient, "method"> & { method: Exclude<NTWRK_METHODS, NTWRK_METHODS.GET>; }): UseMutationResult<TData, TError, TVariables>;
export function makeNetworkCall<TData = any,TError extends Error = Error,TVariables = any>(props: IUseNetworkCallWithClient): UseQueryResult<TData, TError> | UseMutationResult<TData, TError, TVariables>;
export function makeNetworkCall(props: IUseNetworkCallWithoutClient) {
  const { method, queryKey, url, persist, params = "" } = props;
  const config = {
    axiosClient,
    queryKey: queryKey,
    method: method,
    url: url,
    persist: persist,
    params: params,
  };

  return useNetworkQuery(config);
}

Наконец, вы можете вызвать хук makeNetworkCall из своих компонентов:

const getQuery = makeNetworkCall({ method: NTWRK_METHODS.GET }); // Type is UseQueryResult.
const postQuery = makeNetworkCall({ method: NTWRK_METHODS.POST }); // Type is UseMutationResult.

В качестве примечания относительно крючка useAuthCalls...

А как насчет замены на:

export const useAuthCalls = () => {
  const logInMutation = makeNetworkCall({
    method: NTWRK_METHODS.POST,
    url: "/users",
    queryKey: "login",
    persist: true,
    params: "",
  });

  return {
    logIn: logInMutation.mutateAsync,
  };
};

Таким образом, вы всегда используете перехватчик makeNetworkCall и возвращаете mutateAsync только для вызова logIn. Вы сможете без проблем вызывать это из своих обработчиков действий.

В качестве предложения переименуйте свой makeNetworkCall во что-то, что начинается с use. Причина этого в том, чтобы напомнить вам, что на самом деле это крючок и к нему следует относиться как к таковому. Это означает, что его всегда следует вызывать по порядку и только внутри компонентов React.

Если это поможет вам лучше это представить, вот CodeSandbox, который вы можете проверить:

Это сработало отлично, но позже я понял, что использовать перехватчики в условных операторах — не лучшая практика, поэтому теперь я использую два разных метода. Хотя решение, которое вы дали, было правильным

Keshav Khetan 05.07.2024 19:29

Другие вопросы по теме