인라인 함수는 Custom Hook에서 사용할 수 있는 다른 변수를 닫을 수 있기에 queryFn
에 전달하는 가장 쉬운 방법입니다.
type State = 'all' | 'open' | 'done'
type Todo = {
id: number
state: TodoState
}
type Todos = ReadonlyArray<Todo>
const fetchTodos = async (state: State): Promise<Todos> => {
const response = await axios.get(`todos/${state}`)
return response.data
}
export const useTodos = () => {
// imagine this grabs the current user selection
// from somewhere, e.g. the url
const { state } = useTodoParams()
// ✅ The queryFn is an inline function that
// closures over the passed state
return useQuery(['todos', state], () => fetchTodos(state))
}
🚨 매개변수가 많은 경우 상당한 문제를 발생할 수 있습니다.
type Sorting = 'dateCreated' | 'name'
const fetchTodos = async (
state: State,
sorting: Sorting
): Promise<Todos> => {
const response = await axios.get(`todos/${state}?sorting=${sorting}`)
return response.data
}
쿼리에 정렬을 추가하려고합니다.
➡ fetchTodos에서 Custom Hook에서 오류가 발생할 수 있습니다.
쿼리 키가 실제 종속성과 다를 수 있는 복잡한 문제가 발생할 수 있습니다.😡
export const useTodos = () => {
const { state, sorting } = useTodoParams()
// 🚨 can you spot the mistake ⬇️
return useQuery(['todos', state], () => fetchTodos(state, sorting))
}
해결방법?
모든 종속성을 포함하여 쿼리 키를 생성하여 이 문제를 해결하는 babel-plugin-react-query-key-gen 있습니다.
💡 react query에는 종속성을 처리하는 다른 기본 메서드가 포함되어 있습니다.
QueryFunctionContext
사용하는 것입니다.
QueryFunctionContext
는 queryFn
에 인수로 전달되는 객체입니다.
const fetchTodos = async ({ queryKey }) => {
// 🚀 we can get all params from the queryKey
const [, state, sorting] = queryKey
const response = await axios.get(`todos/${state}?sorting=${sorting}`)
return response.data
}
export const useTodos = () => {
const { state, sorting } = useTodoParams()
// ✅ no need to pass parameters manually
return useQuery(['todos', state, sorting], fetchTodos)
}
전체 타입의 안정성을 확보하고 useQuery에 전달된 queryKey에서 QueryFunctionContext
의 타입을 추론하는 것입니다.
export const useTodos = () => {
const { state, sorting } = useTodoParams()
return useQuery(
['todos', state, sorting] as const,
async ({ queryKey }) => {
const response = await axios.get(
// ✅ this is safe because the queryKey is a tuple
`todos/${queryKey[1]}?sorting=${queryKey[2]}`
)
return response.data
}
)
}
🚨 여전히 많은 단점이 있습니다.
queryKey
를 사용하여 위의 방법으로 URL을 작성하는 것은 모든 문자열을 지정할 수 있으므로 여전히 안전하지 않습니다.키를 빌드하기 위한 typesafe query key factory
가 있는 경우 반환 타입을 사용하여 QueryFunctionContext
를 입력할 수 있도록 합니다.
참고 : react-query-querykey
// index.ts
import {
mergeQueryKeys,
inferQueryKeyStore,
} from "@lukemorales/query-key-factory";
export const postQueryKeys = createQueryKeys("posts", {
getPosts: (page?: number, limit?: number) => ({
queryKey: [{ page, limit }],
}),
});
// postTypes.ts
export const queries = mergeQueryKeys(postQueryKeys);
export type QueryKeys = inferQueryKeyStore<typeof queries>;
export type GetPostListQuery = QueryKeys["posts"]["getPosts"];
export interface Post {
userId: number;
id: number;
title: string;
body: string;
}
// queries.ts
const getPostsApi = async (
ctx: QueryFunctionContext<GetPostListQuery["queryKey"]>,
): Promise<Post[]> => {
const [, , { page, limit }] = ctx.queryKey;
const response = await axiosClient.get<Post[]>(
"https://jsonplaceholder.typicode.com/posts",
{
params: { _page: page, _limit: limit },
},
);
return response.data;
};
const useGetPosts = (page: number, limit: number) => {
return useQuery(
queries.posts.getPosts(page, limit).queryKey,
getPostsApi,
);
};
QueryFunctionContext
타입은 react query에 의해 export 됩니다.
queryKey의 타입을 정의하는 하나의 제네릭
을 사용합니다.
해당 구조에 맞지 않는 키를 사용하면 타입 오류가 발생하게 됩니다.
참조