useQuery
, useMutation
등 API 로직을 컴포넌트에서 분리해서, custom hook으로 빼고 싶은데 타입을 어떡하지?..
너무 너무 복잡하다 🤣
// 우리 서비스 코드
// usePost
import { useQuery, QueryKey, UseQueryOptions } from 'react-query';
import axios, { AxiosError, AxiosResponse } from 'axios';
import QUERY_KEYS from '@/constants/queries';
// 특정 id의 post를 가져오는 query
const usePost = ({
storeCode,
options,
}: {
storeCode: QueryKey; // query key에 넣어줄 배열값
options?: UseQueryOptions<AxiosResponse<Post>, AxiosError, Post, QueryKey[]>; // useQuery의 options
}) =>
useQuery([QUERY_KEYS.POST, storeCode], ({ queryKey: [_, id] }) => axios.get(`/posts/${id}`), {
select: data => data.data,
...options,
});
export default usePost;
storeCode와 options의 타입을 알아야한다.
QueryKey
타입 type QueryKey = string | readonly unknown[]
UseQueryOptions
라는 타입이 지원된다.
export interface UseQueryOptions
<TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey> extends UseBaseQueryOptions ...
{
}
대충 이렇게 제네릭이 이루어져 있다.
useQueryOptions
extends UseBaseQueryOptions
extends QueryObserverOptions
.. 이므로 따라 따라 가보면
오 드디어 옵션들이 보인다..!
query: Query<TQueryFnData, TError, TQueryData, TQueryKey>
onError?: (err: TError) => void;
onSuccess?: (data: TData) => void;
// 뇌피셜
TQueryFnData : query function이 뱉는 data니깐 AxiosResponse
TError : onError의 매개변수로 들어가는 값의 타입이므로 AxiosError
TData : useQuery가 뿜는 data니깐 TQueryFnData와 같거나 정제된 데이터
TQueryKey : 지원되는 QueryKey타입을 쓰면 되겠다. 어차피 default type이 QueryKey인듯
// 우리 서비스 코드
// useCreatePost
import { useContext } from 'react';
import { useQueryClient, useMutation, UseMutationOptions } from 'react-query';
import axios, { AxiosError, AxiosResponse } from 'axios';
import SnackbarContext from '@/context/Snackbar';
import QUERY_KEYS from '@/constants/queries';
const useCreatePost = (
options?: UseMutationOptions<AxiosResponse<string, string>, AxiosError, Pick<Post, 'title' | 'content'>>,
) => {
const queryClient = useQueryClient();
const { showSnackbar } = useContext(SnackbarContext);
return useMutation(
({ title, content }: { title: string; content: string }): Promise<AxiosResponse<string, string>> =>
axios.post('/posts', {
title,
content,
}),
{
...options,
onSuccess: (data, variables, context) => {
queryClient.resetQueries(QUERY_KEYS.POSTS);
showSnackbar('글 작성에 성공하였습니다.');
if (options && options.onSuccess) {
options.onSuccess(data, variables, context);
}
},
},
);
};
export default useCreatePost;
useMutation
의 options를 위해 UseMutationOptions
타입이 지원된다.
이번에는 깃허브를 참고해서 타입을 추론해봤다.
export interface UseMutationOptions<
TData = unknown,
TError = unknown,
TVariables = void,
TContext = unknown
> {
mutationKey?: string | unknown[]
onMutate?: (variables: TVariables) => Promise<TContext> | Promise<undefined> | TContext | undefined
onSuccess?: (
data: TData, // 1️⃣
variables: TVariables,
context: TContext | undefined
) => Promise<void> | void
onError?: (
error: TError, // 2️⃣
variables: TVariables,
context: TContext | undefined
) => Promise<void> | void
onSettled?: (
data: TData | undefined,
error: TError | null,
variables: TVariables,
context: TContext | undefined
) => Promise<void> | void
retry?: RetryValue<TError>
retryDelay?: RetryDelayValue<TError>
useErrorBoundary?: boolean
}
3️⃣
// 뇌피셜
TData : 1️⃣ useMutation이 뱉는 data, AxiosResponse아니면 정제된 데이터일 것이다.
TError : 2️⃣ AxiosError
TVariables : 3️⃣ 잘 모르겠어서 출력해봤다.. request body로 보내준 데이터가 출력이 되었다. 그래서 그 데이터의 타입을 입력해주었다.
TContext : onMutate가 리턴하는 객체를 context 파라미터로 참조가 가능하다고 하다. 쓰이지 않아서 unknown으로 냅뒀다.
//AxiosResponse<T, D>
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: AxiosResponseHeaders;
config: AxiosRequestConfig<D>;
request?: any;
}
export interface AxiosRequestConfig<D = any> {
url?: string;
method?: Method | string;
baseURL?: string;
transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: AxiosRequestHeaders;
params?: any;
paramsSerializer?: (params: any) => string;
data?: D;
..
이것도 잘 가늠이 안돼서 출력해보았다.!..!
아하!
도대체 타입추론하는 쉬운 방법은 뭘까? 하하
좋은 글 감사합니다!
매번 커스텀 훅으로 useQuery를 만들 때 마다, 작성해주신 타입을 지정해서 사용하면 코드가 좀 길어지고 번거로워서
와 같은 형태로 두고, 사용하면 더 편하더라구요!