[TS] useQuery, useMutation์ ์ ๋ค๋ฆญ ํ์ ์ดํด๋ณด๊ธฐ / Base Query ํ ๋ง๋ค๊ธฐ
React Query์์ ์ ๊ณตํ๋ useQuery, useMutation์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด ํญ์ ์ฟผ๋ฆฌํค์ ์ฟผ๋ฆฌ ํจ์๋ฅผ ์ง์ ํด์ผ ํ๋ ๋ฒ๊ฑฐ๋ก์์ด ์๋ค. API ์ ํ์ ๋ฐ๋ผ Base Query/Mutation ์ปค์คํ
ํ
์ ๋ง๋ค์ด ๋๊ณ ์ฌ์ฉํ๋ฉด ์ฟผ๋ฆฌํค์ ์ฟผ๋ฆฌ ํจ์๋ฅผ ์ผ์ผ์ด ์ง์ ํ์ง ์๊ณ ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฌ์ฉํ๊ธฐ ์ข๋ค.
ํ์ง๋ง ์ปค์คํ
ํ
์ ๋ง๋ค ๋ ์ฟผ๋ฆฌ ํ์
์ ์ง์ ํ์ง ์์ผ๋ฉด ๋ฐ์ดํฐ๊ฐ unknown, any์ธ ๊ฒฝ์ฐ๊ฐ ๋ง์์ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋์ง ์๊ธฐ ์ด๋ ต๋ค. ์ฟผ๋ฆฌ ํ์
์ ๋๋ถ๋ถ ์ ๋ค๋ฆญ์ผ๋ก ๋์ด ์๋๋ฐ ์ด๋ฅผ ์ ์ฌ์ฉํ๋ฉด ํ์
์ ๋ณด์ฅ๋ฐ์ผ๋ฉด์ ๋ฐ์ดํฐ๋ฅผ ํธ๋ฆฌํ๊ฒ ๋ค๋ฃฐ ์ ์๋ค.
useQuery
์ ๋ค๋ฆญ ํ์ ํบ์๋ณด๊ธฐ โก๏ธ
export declare function useQuery<
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey // string | readonly unknown[];
>
โถ TQueryFnData : ์ฟผ๋ฆฌ ํจ์ ๋ฆฌํด ํ์
(AxiosResponse<T> ๋ฑ)
โท TError : ์ฟผ๋ฆฌ ํจ์ ์๋ฌ ํ์
(AxiosError ๋ฑ)
const { data, error } = useQuery<number, AxiosError>("todo", getTodo);
// data ํ์
: number | undefined
// error ํ์
: AxiosError | null
โธ TData : select ํจ์๋ก ์ฟผ๋ฆฌ ํจ์ ๋ฆฌํด๊ฐ์ ๊ฐ๊ณตํ ๋ ์ฌ์ฉํ๋ ์ต์ข
๋ฆฌํด ํ์
. ๊ธฐ๋ณธ๊ฐ TQueryFnData
TQueryFnData๋ ์ฟผ๋ฆฌ ํจ์๊ฐ ๋ฐํํ๋ ์๋ณธ ๋ฐ์ดํฐ ํ์ ์ด๊ณ ,TData๋ ์ต์ข ์ ์ผ๋ก ์ฌ์ฉํ๊ฒ ๋ ๋ฐ์ดํฐ ํ์ ์ด๋ค.select์ต์ ์ ์ฌ์ฉํ๋ฉด ์ฟผ๋ฆฌ ํจ์๊ฐ ๋ฐํํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ ์ ์์ผ๋ฏ๋ก,TData๋TQueryFnData์ ๋ฌ๋ผ์ง ์ ์๋ค. — TanStack
const { data } = useQuery<Todo[], AxiosError, number>("todos", getTodos, {
select: (todos) => todos.length,
});
// select ํจ์๊ฐ number(todos.length) ํ์
์ ๋ฐํํ๋ฏ๋ก
// TData(3๋ฒ์งธ ์ ๋ค๋ฆญ ํ์
) ํ์
๋ number ํ์
์ผ๋ก ์ง์ ํด์ค๋ค
โน TQueryKey : ์ฟผ๋ฆฌ ํค ํ์
. ๊ธฐ๋ณธ๊ฐ์ ๋ฌธ์์ด ํน์ ๋ฐฐ์ด.
์ฟผ๋ฆฌํค ๋ฐฐ์ด ๊ฐ ์์์ ์ด๋ค ํ์
์ด ๋ค์ด๊ฐ์ง TQueryKey ํ์
์ ์ง์ ์ง์ ํ ์ ์๋ค.
useQuery<Todo[], AxiosError, Todo[], [string, number]>(["todos", id], getTodo);
์ฐธ๊ณ ๋ก QueryKey ํ์
์ ๋ฌธ์์ด ํน์ ๋ฐฐ์ด์ด์ง๋ง ์ฟผ๋ฆฌ ํจ์์ context ์ธ์์ ํญ์ ๋ฐฐ์ด๋ก ์ ๋ฌ๋๋ค. ์ฆ, ์ฟผ๋ฆฌํค๋ฅผ ๋ฌธ์์ด๋ก ์ง์ ํด๋ ์ฟผ๋ฆฌ ํจ์์ ํญ์ ๋ฐฐ์ด๋ก ์ ๋ฌ๋๋ ๊ฒ. ์: 'todo' → ['todo']
queryFn: (context: QueryFunctionContext) => Promise<TData>
Base Query ์ปค์คํ ํ
ํ๋ผ๋ฏธํฐ๊ฐ ์์ ๋
ํ
์ ์ฌ์ฉํ๋ ๊ณณ์์ ์ฟผ๋ฆฌ ์ต์
์ ๋๊ธธ ์ ์๋๋ก options ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํ๋ค. ์ฟผ๋ฆฌ ์ต์
์ React Query์์ ์ ๊ณตํ๋ UseQueryOptions ํ์
์ import ํด์ ์ฌ์ฉํ๋ฉด ๋๋ค. UseQueryOptions, useQuery ๋์ ํ์
์ธํฐํ์ด์ค๋ ๋์ผํ๋ค.
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>
// ์ปค์คํ
ํ
์ ์
import { useQuery, UseQueryOptions } from "react-query";
// ...
export default function useClientQuery<T = Client[]>(
options?: UseQueryOptions<Client[], AxiosError, T>,
) {
const { getClientList } = ClientAPI;
return useQuery<Client[], AxiosError, T>(
queryKeys.CLIENT_LIST,
getClientList, // resolved data๋ฅผ ๋ฐํํ๋ ์ฟผ๋ฆฌ ํจ์
options,
);
}
useClientQuery ์ ๋ค๋ฆญ ํ์
T์ ๊ธฐ๋ณธ๊ฐ์ Client[]๋ก ์ค์ ํ์ผ๋ฏ๋ก select ํจ์๋ฅผ ๋๊ธฐ์ง ์์์ ๋ data ํ์
์ Client[]๊ฐ ๋๋ค. select ํจ์๋ฅผ ๋๊ธฐ๋ฉด select ํจ์๊ฐ ๋ฐํํ๋ ๊ฐ์ ํ์
์ด data ํ์
์ด ๋๋ค.
๐ก select ํจ์ ์ธ์์ ์ฟผ๋ฆฌ ํจ์์ ๋ฆฌํด๊ฐ์ด ์ ๋ฌ๋๋ค. ์ ์์์์ Client[] ํ์
์ data๊ฐ ์ ๋ฌ๋๋ค. ์ฐธ๊ณ ๋ก select ํจ์๋ ์บ์์ ์ ์ฅ๋๋ ๋ด์ฉ์๋ ์ํฅ์ ์ฃผ์ง ์๊ณ , ๋ฐํํ ๋ฐ์ดํฐ ๊ฐ์๋ง ์ํฅ์ ์ค๋ค.
// ์ปค์คํ
ํ
์ฌ์ฉ
// ๋ค๋ฅธ ์ปดํฌ๋ํธ์์ ์ปค์คํ
ํ
์ ์ฌ์ฉํ ๋
const selectFn = (data: Client[]) => { /* ... */ };
const { data, ...queryResults } = useClientQuery({ select: selectFn /* ... */ });
ํ๋ผ๋ฏธํฐ๊ฐ ์์ ๋
URL์ ๋์ path๋ฅผ ์ถ๊ฐํด์ผ ํ๋ API๋ผ๋ฉด(/orders/{userId}) Base Query ํ
์ด path๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ์ ์๋๋ก ํ๊ณ ์ฟผ๋ฆฌ ํจ์๋ก path๋ฅผ ๋๊ธฐ๋ฉด ๋๋ค. ์๋ ์์์์ ์ฟผ๋ฆฌ ํจ์๊ฐ resolved data๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ์ฟผ๋ฆฌ ํจ์๋ฅผ async () => (await queryFn(params)).data ํํ๋ก ์์ฑํ๋ค.
// ์ปค์คํ
ํ
์ ์
import { useQuery, UseQueryOptions } from "react-query";
// ...
export default function useOrderQuery<T = Order[]>(
userId: number,
options?: UseQueryOptions<Order[], AxiosError, T>,
) {
const { getOrdersById } = OrderAPI;
return useQuery<Order[], AxiosError, T>(
queryKeys.ORDER_LIST,
async () => (await getOrdersById(userId)).data, // resolved data๋ฅผ ๋ฐํํ๋ ์ฟผ๋ฆฌ ํจ์
options,
);
}
์ฟผ๋ฆฌ ํจ์ ๋ฐํ๊ฐ AxiosResponse vs Resolved Data โก๏ธ
์ฟผ๋ฆฌ ํจ์๊ฐ AxiosResponse๋ฅผ ๋ฐํํ๋ฉด ์ฟผ๋ฆฌ์ TQueryFnData ํ์
๋ ๋์ผํ๊ฒ ์ง์ ํด์ผ ํ๋ค.
//์ฟผ๋ฆฌ ํจ์๊ฐ AxiosResponse๋ฅผ ๋ฐํํ ๋
// AxiosResponse๋ฅผ ๋ฐํํ๋ ์ฟผ๋ฆฌ ํจ์
const getClientList = async () => {
return await axios.get<Client[]>("clients");
};
// useClientQuery ํ
return useQuery<AxiosResponse<Client[]>, AxiosError, T>(/* ... */);
์ฟผ๋ฆฌ ํจ์๊ฐ resolved data๋ฅผ ๋ฐํํ๋ฉด TQueryFnData ํ์
์ ๋ฐํ ๋ฐ์ดํฐ ํ์
(Client[])๋ง ์ง์ ํ๋ฉด ๋๋ค.
// ์ฟผ๋ฆฌ ํจ์๊ฐ resolved data๋ฅผ ๋ฐํํ ๋
// resolved data๋ฅผ ๋ฐํํ๋ ์ฟผ๋ฆฌ ํจ์
const getClientList = async () => {
const { data } = await axios.get<Client[]>("clients");
return data;
};
// useClientQuery ํ
return useQuery<Client[], AxiosError, T>(/* ... */);
useMutation
์ ๋ค๋ฆญ ํ์ ํบ์๋ณด๊ธฐ โก๏ธ
export function useMutaion<
TData = unknown,
TError = unknown,
TVariables = void,
TContext = unknown
>
โถ TData : mutation ํจ์ ์คํ ๊ฒฐ๊ณผ ํ์
(AxiosResponse<T> ๋ฑ)
TData ํ์
์ โถonSuccess ์ฝ๋ฐฑ์ ์ธ์ ํ์
๊ณผ โทuseMutation ํ
์ด ๋ฐํํ๋ data ํ์
์ด ๋๋ค.
const { data } = useMutation<AxiosResponse<boolean>>(postTodo, {
onSuccess: (res) => { /* ... */ },
});
// data ํ์
: AxiosResponse<boolean> | undefined
// onSuccess ์ฝ๋ฐฑ์ res ์ธ์ ํ์
: AxiosResponse<boolean>
โท TError : mutation ํจ์ ์๋ฌ ํ์
(AxiosError ๋ฑ)
TError ํ์
์ โถonError ์ฝ๋ฐฑ์ ์ธ์ ํ์
๊ณผ โทuseMutation ํ
์ด ๋ฐํํ๋ error ํ์
์ด ๋๋ค.
const { error } = useMutation<AxiosResponse<boolean>, AxiosError>(postTodo, {
onError: (err) => { /* ... */ },
});
// error ํ์
: AxiosError | null
// onError ์ฝ๋ฐฑ์ err ์ธ์ ํ์
: AxiosError
โธ TVariables : mutation ํจ์์ ์ธ์ ํ์
TVariables ํ์
์ mutate ํจ์์ onSuccess ๋ฑ ์ต์
๋ฉ์๋ ์ฝ๋ฐฑ ํจ์์ ์ธ์ ํ์
์ด ๋๋ค.
const { mutate } = useMutation<AxiosResponse<boolean>, AxiosError, number>(
postTodo, // mutationFn
{
onSuccess: (res, id) => { /* ... */ },
onError: (err, id) => { /* ... */ },
onMutate: (id) => { /* ... */ },
onSettled: (res, err, id) => { /* ... */ },
},
);
const onClick = () => mutate(8);
โน TContext : onMutate ์ฝ๋ฐฑ ํจ์์ ๋ฆฌํด ํ์
onMutate ์ฝ๋ฐฑ ํจ์๋ mutation ํจ์ ์คํ ์ ์ ์คํ๋๋ฉฐ, onMutate ๊ฒฐ๊ณผ ๊ฐ์ onSuccess ๊ฐ์ ์ต์
๋ฉ์๋์ ์ฝ๋ฐฑ ํจ์ ์ธ์๋ก ์ ๋ฌ๋๋ค.
const { mutate } = useMutation<
AxiosResponse<boolean>,
AxiosError,
number,
number
>(postTodo, {
onSuccess: (res, id, nextId) => { /* ... */ },
onError: (err, id, nextId) => { /* ... */ },
onMutate: (id) => id + 1, // onMutate ์ฝ๋ฐฑ ํจ์์ ์ธ์(id) ํ์
์ TVariables
onSettled: (res, err, id, nextId) => { /* ... */ },
});
// onSuccess, onMutate, onSettled ์ฝ๋ฐฑ์ nextId ์ธ์๋ onMutate์ ๋ฆฌํด๊ฐ(number)
Base Mutation ์ปค์คํ ํ
Base Query ์ปค์คํ
ํ
์ ์ ์ํ ๊ฒ์ฒ๋ผ ํ
์ ์ฌ์ฉํ๋ ๊ณณ์์ mutation ์ต์
์ ๋๊ธธ ์ ์๋๋ก options ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํ๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก UseQueryOptions ํ์
์ importํด์ ์ฌ์ฉํ๋ค.
invalidateQueries๋ ํ๋ก ํธ์๋์์ ์ฒ๋ฆฌํ ์ฝ๋๊ฐ ์ ์ ๋์ , ์ฟผ๋ฆฌ๋ฅผ ๋ค์ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ๋คํธ์ํฌ ์์ฒญ์ด ํ ๋ฒ ๋ ๋ฐ์ํ๋ค. ๋ฐ๋๋กsetQueryData๋ ์ถ๊ฐ ์์ฒญ ์์ด ์บ์๋ฅผ ์ง์ ๊ฐฑ์ ํ ์ ์์ง๋ง, ์๋ต ๋ฐ์ดํฐ๋ฅผ ํ์ฌ ์บ์์ ๋ง๊ฒ ์ง์ ๋ฐ์ํ๋ ์ฝ๋๊ฐ ํ์ํ๋ค. (์ฐธ๊ณ ๋งํฌ)
mutation ํจ์๋ฅผ ํธ์ถํ ์ดํ ๊ธฐ๋ณธ์ ์ผ๋ก ์ํํ ์์
์ด ์๋ค๋ฉด onSuccess, onError ๊ฐ์ ์ต์
๋ฉ์๋๋ฅผ ๋ฏธ๋ฆฌ ์ ์ํด ๋ ์๋ ์๋ค. ์ฐธ๊ณ ๋ก mutation์ ๋ช
์ํ ์ฟผ๋ฆฌํค๋ฅผ stale ๋ฐ์ดํฐ๋ก ์ทจ๊ธํ๋๋ก ํด์ re-fetching์ ํธ๋ฆฌ๊ฑฐํ๋ queryClient.invalidateQueries ๋ฉ์๋์ ์์ฃผ ์ฌ์ฉํ๋ค.
// ์ปค์คํ
ํ
์ ์
import { useMutation, UseQueryOptions } from "react-query";
// ...
export default function useAddTodoMutation(
options?: UseMutationOptions<PostTodoResponse, AxiosError, PostTodoPayload>,
) {
// PostTodoResponse ํ์
: AxiosResponse<boolean>
// PostTodoPayload ํ์
: number
return useMutation<PostTodoResponse, AxiosError, PostTodoPayload>(
// AxiosResponse๋ฅผ ๋ฐํํ๋ mutation ํจ์ ↓
// id ํ๋ผ๋ฏธํฐ ํ์
์ PostTodoPayload ↓
(id) => postTodo(id),
options,
);
}
useAddTodoMutation ํ
์ด ๋ฐํํ๋ mutate ํจ์๋ก mutation์ ์คํ์ํฌ ์ ์๋ค. mutate ํจ์์ ์ฒซ ๋ฒ์งธ ์ธ์๋ mutation ํจ์๋ก ์ ๋ฌ๋๋ฉฐ, useMutation 3๋ฒ์งธ ์ ๋ค๋ฆญ ํ์
์ผ๋ก ๋๊ธด PostTodoPayload ํ์
(TVariables)์ ๊ฐ๋๋ค. ํ์์ ๋ฐ๋ผ onSuccess ๊ฐ์ ์ต์
๋ฉ์๋๋ฅผ ๋๊ธธ ์๋ ์๋ค.
// ์ปค์คํ
ํ
์ฌ์ฉ
const onSuccess = () => { /* ... */ };
const { mutate, ...useMutationResults } = useAddTodoMutation({ onSuccess });
const onClick = (id: PostTodoPayload) => mutate(id);
// mutate ํจ์ ๋๋ฒ์งธ ์ธ์์ { onError, onSettled, onSuccess }๋ฅผ ๋๊ธธ ์๋ ์๋ค.
// ex) mutate(id, { onSuccess: () => {} })
// ์คํ ์์ : useMutation ์ต์
๋ฉ์๋ -> mutate ์ต์
๋ฉ์๋
์ ๋ค๋ฆญ์ผ๋ก ๊น๋ํ๊ฒ ์์ฑํ๊ธฐ โก๏ธ
useQuery|Mutation์ ์ ๋ค๋ฆญ์ ์ฌ์ฉํ๋ ค๋ฉด ์ฟผ๋ฆฌ(API) ํจ์๋ ์ ๋ค๋ฆญ์ ๋ฐ์ ์ ์๋๋ก ์์ ํด์ผ ํ๋ค.
const getClientList = async <T = Client[],>() => {
const { data } = await axios.get<T>("clients");
return data;
};
Query ํ
์ ์ ๋ค๋ฆญ T๋ ์ฟผ๋ฆฌ ํจ์ ๋ฆฌํด ํ์
(TQueryFnData)์ผ๋ก, K๋ ์ต์ข
๋ฆฌํด ํ์
(TData)์ผ๋ก ๋ค์ด๊ฐ๋๋ก ์์ ํ๋ค. T, K ๋ชจ๋ ๊ธฐ๋ณธ๊ฐ์ ์ง์ ํ์ผ๋ฏ๋ก, ์ง์ ํ ํ์
๊ณผ ๋์ผํ๋ค๋ฉด ๋ฐ๋ก ๋๊ธฐ์ง ์์๋ ๋๋ค.
// useQuery ์ ๋ค๋ฆญ ์ฌ์ฉ ์์
export default function useClientQuery<T = Client[], K = T>(
options?: UseQueryOptions<T, AxiosError, K>,
) {
const { getClientList } = ClientAPI;
return useQuery<T, AxiosError, K>(
queryKeys.CLIENT_LIST,
getClientList,
options,
);
}
Mutation ํ
์ ์ ๋ค๋ฆญ T๋ mutation ํจ์ ์คํ ๊ฒฐ๊ณผ ํ์
(TData)์ผ๋ก, K๋ mutation ํจ์์ ์ธ์ ํ์
(TVariables)์ผ๋ก ๋ค์ด๊ฐ๋๋ก ์์ ํ๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก ์ฟผ๋ฆฌ ํจ์๊ฐ ์ ๋ค๋ฆญ์ ๋ฐ์ ์ ์์ด์ผ ํ๋ค.
// useMutation ์ ๋ค๋ฆญ ์ฌ์ฉ ์์
export default function useAddTodoMutation<
T = PostTodoResponse,
K = PostTodoPayload,
>(options?: UseMutationOptions<T, AxiosError, K>) {
return useMutation<T, AxiosError, K>((id) => postTodo(id), options);
}
๋ ํผ๋ฐ์ค
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[TS] ์๋ฆฌ๋จผํธ์ ๊ธฐ๋ณธ ์ดํธ๋ฆฌ๋ทฐํธ ์ธํฐํ์ด์ค/ํ์ ์ฌ์ฉํ๊ธฐ
[TS] ์๋ฆฌ๋จผํธ์ ๊ธฐ๋ณธ ์ดํธ๋ฆฌ๋ทฐํธ ์ธํฐํ์ด์ค/ํ์ ์ฌ์ฉํ๊ธฐ
2024.05.12 -
[JS] ์ JSX ์์์ if ๋ฌธ์ ์ฌ์ฉํ ์ ์์๊น? ํํ์๊ณผ ๋ฌธ ์ฐจ์ด์
[JS] ์ JSX ์์์ if ๋ฌธ์ ์ฌ์ฉํ ์ ์์๊น? ํํ์๊ณผ ๋ฌธ ์ฐจ์ด์
2024.05.12 -
[React] ๋ฆฌ์กํธ ์ฟผ๋ฆฌ(React Query) staleTime์ ์ค์ ํ๋ 3๊ฐ์ง ๋ฐฉ๋ฒ
[React] ๋ฆฌ์กํธ ์ฟผ๋ฆฌ(React Query) staleTime์ ์ค์ ํ๋ 3๊ฐ์ง ๋ฐฉ๋ฒ
2024.05.12 -
[Git] ๋ณํฉ(Merge) ์ถฉ๋ ๋ฐฉ์ง๋ฅผ ์ํ ๋ฆฌ๋ฒ ์ด์ค Rebase
[Git] ๋ณํฉ(Merge) ์ถฉ๋ ๋ฐฉ์ง๋ฅผ ์ํ ๋ฆฌ๋ฒ ์ด์ค Rebase
2024.05.12