React Query(TanstackQuery)는 단순히 "API 호출을 편하게 해주는 라이브러리" 가 아니라
서버 상태(server state)를 캐시로 관리하는 도구다.
그래서 React Query 를 조금 더 알고 쓰려면 useQuery 문법보다 먼저 이해해야하는게 있다.
queryKey = 캐시에 저장되는 데이터의 주소(식별자)
이 글에서는 React Query의 핵심 개념과,
특히 queryKey를 미리 세팅 (키 팩토리/컨벤션화) 해두면 얻는 장점을 정리해보려고 한다.
사실, 해당 내용을 인지하기 전 개인프로젝트에서 queryKey 관련으로 골머리 아픈 상황이 생겼고, 이를 방지하기 위해(다시는 그런 일을 겪지 않기 위해...) 정리하기로 마음 먹었다.
프론트에서 상태는 크게 두 종류로 나뉜다.
React Query 는 후자인 서버 상태 를 캐시로 다루는 데 특화돼 있다.
React Query에서 queryKey는 어떤 데이터를 캐시에 저장할지 결정한다.
그래서 queryKey를 대충 쓰면
반대로 queryKey를 잘 설계하면
나는 queryKey를 컴포넌트에서 직접 하드코딩하지 않고, 한 파일에 모아두는 방식을 사용했다.
export const lessonKeys = {
// 내 레슨 목록(정렬 기준을 key에 포함)
myList: (sort: LessonSort) => ["myLessons", sort] as const,
// 내 레슨 상세
myDetail: (lessonId: number) => ["lessons", "me", "detail", lessonId] as const,
// 타임슬롯(기간 포함)
myTimeSlots: (lessonId: number, from: string, to: string) =>
["lessons", "me", "detail", lessonId, "time-slots", from, to] as const,
// 반복 규칙
myRecurrence: (lessonId: number) =>
["lessons", "me", "detail", lessonId, "recurrence"] as const,
};
참고 *: 프로젝트 중간에 컨벤션을 도입하면 기존에 쓰던 키(prefix)와 섞이기 쉬운데, 이때 같은 데이터를 서로 다른 key로 캐싱하게 되면서 invalidate가 먹지 않는 문제가 생길 수 있다.
그런의미에서 초반에 잡아두고 가는것이 좋다고 판단되었다.
예를 들어 내 레슨 목록만 갱신하고 싶다면
qc.invalidateQueries({ queryKey: lessonKeys.myList(sort) });
정렬 캐시를 한 번에 갱신하고 싶으면 prefix를 기준으로
qc.invalidateQueries({ queryKey: ["myLessons"] });
invalidate 전략을 코드로 설명가능하게 된다.
협업에서 실수(키 불일치)를 크게 줄인다
키를 하드코딩하면 사람이 늘어날수록 "미묘하게 다른 배열"이 생긴다.
keys factory로 통일하면 팀 전체가 같은 key를 쓰게되면서 해당 문제를 줄일 수 있다.
리팩토링이 용이하다
나중에 key구조를 바꿔야 해도 한 파일만 수정하면 된다.
키를 미리 세팅해두면 useQuery는 오히려 단순해진다.
export function useMyLessonList(sort: LessonSort) {
return useQuery({
queryKey: lessonKeys.myList(sort),
queryFn: async (): Promise<LessonSummary[]> => {
const resp = await getMyLessonsReq();
return resp.data.data;
},
});
}
여기서 핵심은
서버 데이터를 바꾸는 작업은 useMutation이 담당한다.
export function useToggleMyLessonStatus(sort: LessonSort) {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ lessonId, next }: { lessonId: number; next: LessonStatus }) =>
updateMyLessonReq(lessonId, { status: next }),
onSuccess: (_data, vars) => {
// 목록 + 상세 캐시를 다시 최신화
qc.v({ queryKey: lessonKeys.myList(sort) });
qc.invalidateQueries({ queryKey: lessonKeys.myDetail(vars.lessonId) });
},
});
}
여기서 중요한 건 "쓰기 성공 이후 어떤 캐시를 갱신할지"가 key로 드러난다는 점이다.
React Query를 "조금 더 알고" 사용한다는 말은 결국 이 말로 요약된다.
여기서 나는 미리 세팅 혹은, keys를 파일로 분리할 생각을 하지 못했었고 코드와 파일이 늘어날 수록 이전에 설정해놓은 key를 다시 설정한다던가 하는 실수를 해서 상태를 최신화 시키지 못하는 상황이 일어났었다.
다시는 그로 인해 쓸데없이 정신력 깎아먹는 시간을 만들지 않도록 성장해야겠다!
끝