
프로젝트를 진행하던도중 Query Function 에 매개변수가 많아져서 리팩토링이 필요해졌다.
따라서 매개변수를 활용하는 방법이 무엇이 있을까 찾아보던 와중 함수 컨텍스트가 존재했다.
QueryFunctionContext 는 queryFn에 인수로 전달되는 객체이다.
const BookmarkCamp = () => {
const params = useParams();
const userId = params.id as string;
const { data, isLoading, isError } = useQuery<LikeCampType>({
queryKey: ['mypage', 'bookmark', 'camp', userId],
queryFn: getUserLikeCamp,
refetchOnWindowFocus: true,
});
import { LikeCampType } from '@/types/profile';
import { QueryFunctionContext } from '@tanstack/react-query';
export const getUserLikeCamp = async ({ queryKey }: QueryFunctionContext): Promise<LikeCampType> => {
const [_, __, ___, userId] = queryKey;
const res = await fetch(`/api/profile/${userId}/camp/like`, {
method: 'GET',
});
const fetchData = await res.json();
return fetchData;
};
객체 구조분해 할당으로 queryKey를 받고 QueryFunctionContext type으로 선언한다.
키를 빌드하기 위한 타입 안전한 쿼리 키 팩토리가 있는 경우,
해당 팩토리의 리턴 타입을 사용하여 QueryFunctionContext를 입력할 수 있다.
const todoKeys = {
all: ['todos'] as const,
lists: () => [...todoKeys.all, 'list'] as const,
list: (state: State, sorting: Sorting) =>
[...todoKeys.lists(), state, sorting] as const,
}
const fetchTodos = async ({
queryKey,
}:
// factory 함수의 리턴값인 키만 허용
QueryFunctionContext<ReturnType<typeof todoKeys['list']>>) => {
const [, , state, sorting] = queryKey
const response = await axios.get(`todos/${state}?sorting=${sorting}`)
return response.data
}
export const useTodos = () => {
const { state, sorting } = useTodoParams()
// ✅ 쿼리키 팩토리로 키를 빌드
return useQuery(todoKeys.list(state, sorting), fetchTodos)
}
위의 예에서는 쿼리키를 키 팩토리의 list 함수가 반환하는 것과 동일하게 설정한다.
const assertions을 사용하기 때문에 모든 키는 엄격하게 타입이 지정된 튜플이 될 것이다.
따라서 해당 구조를 따르지 않는 키를 사용하려고 하면 타입 오류가 발생한다.
배열의 구조분해는 named property를 가져올 때 위험할 수 있다. undefined인지 체크해주지 않는다.
const [, , state, sorting] = queryKey
명명된 분해를 사용할 수 있기 때문에 객체는 이 문제를 정말 잘 해결한다.
또한 쿼리 무효화를 위한 퍼지 일치(추상적인 키를 입력할 수록 매칭 범위가 커지는 일치)가
배열의 경우와 객체에 대해 동일하게 작동하기 때문에 쿼리 키로서 단점이 없다.
const todoKeys = {
// all keys are arrays with exactly one object
all: [{ scope: 'todos' }] as const,
lists: () => [{ ...todoKeys.all[0], entity: 'list' }] as const,
list: (state: State, sorting: Sorting) =>
[{ ...todoKeys.lists()[0], state, sorting }] as const,
}
const fetchTodos = async ({
// extract named properties from the queryKey
queryKey: [{ state, sorting }],
}: QueryFunctionContext<ReturnType<typeof todoKeys['list']>>) => {
const response = await axios.get(`todos/${state}?sorting=${sorting}`)
return response.data
}
export const useTodos = () => {
const { state, sorting } = useTodoParams()
return useQuery(todoKeys.list(state, sorting), fetchTodos)
}
// remove everything related to the todos feature
queryClient.removeQueries([{ scope: 'todos' }])
// reset all todo lists
queryClient.resetQueries([{ scope: 'todos', entity: 'list' }])
// invalidate all lists across all scopes
queryClient.invalidateQueries([{ entity: 'list' }])