
:Query Key가 무슨 호크룩스인가요?
협업을 하면 무엇이 떠오르시나요?
다양한 것이 있겠지만 저는 코드 컨벤션과 같은 것이 우선적으로 생각이 났습니다.
사람들은 각자 가진 코드의 스타일이 있습니다. (개발 쪼라고 할 수도 있겠습니다.)
서로의 스타일이 하나의 서비스에서 충돌한다면 이후 작업자 혹은 신규 작업자는 코드를 읽으며 혼돈에 빠지고 불필요한 시간을 들이게 될 것입니다.
따라서 저는 이번 협업이라는 주제에서 효율성과 일관성을 높일 수 있는 전략에 대해 공유해 보고자 하는 큰 틀을 생각했습니다.
tanstack query (react query v5)로 버전을 올리고 공식 문서와 TkDodo's blog를 읽던 중 이전부터 필요하다고 느꼈던 react query key의 효율적인 관리 방법을 소개하는 것을 보았습니다.
적절한 query key 관리 전략을 통해, 팀원 간의 코드 이해도를 높이고, 전체 프로젝트의 효율성과 유지 보수성을 향상에 중요한 역할을 할 수 있다 생각하여 세부적인 주제로 선택해 보았습니다.
Query Key의 매주 중요한 부분은 QueryCache(쿼리 캐시)와 상호작용하기 때문일 것입니다.
라이브러리 내부적으로 올바른 데이터 캐싱을 가능하게 합니다.
query key를 통해 캐싱된 상태가 동일한 쿼리키가 온다면 새로운 호출 없이 이전의 값을 반환하게 되죠.
그러나 때때로 우리는 수동적인 상호작용이 필요한 경우가 있을 것입니다.
query key가 가장 중요한 부분은 방금 언급한 캐시와 수동적으로 상호작용하는 경우입니다.
invalidate query와 같이 개발자가 수동적으로 쿼리를 조작하고자 할 경우에도 query key관리는 잘 관리되어야합니다.
앞서 이야기한 대로 react query를 이용할 때는 query key를 작성해 줘야 합니다. 그리고 query key의 경우 unique 한 값이어야 합니다.
query key를 활용하여 패치한 데이터를 캐싱하고 불필요한 호출을 줄일 수 있죠. 그러나 캐싱이 매번 좋은 것은 아닙니다. 때때로 우리는 mutation 이후 필요에 따라 query key를 무효화 시키고 싶을 수 있습니다.
(데이터 업데이트 후 새로운 값을 보여줘야하는데 이전에 남아있는 캐싱때문에 새 값을 못보면 안되잖아요!)
그러기 위해선 고유한 쿼리키를 필요에 따라 invalidate로 무효화를 해주어야 합니다.
이렇게 우리는 key를 직접적으로 다루는 일이 꽤나 있습니다.
서비스가 보다 복잡해진다면 query 문과 key는 더더욱 많아져 초장에 잘 다듬어 놓지 않으면 마치 엉킨 실타래처럼 꼬이고 말 것입니다.
규칙 없이 선언된 key들은 어디에 위치하고 있는지 찾는 것에도 오래 걸릴뿐더러, invalidate를 통해서 무효화를 하려 해도 어느 곳에서는 다른 기준으로 선언된 key들로 의도한 대로 작동하지 않을 수 있습니다.
또한 unique 해야 하는 key가 겹친 채로 존재할 수도 있습니다. (위 경우들은 경험담입니다..)
이러한 이유로 시간을 버리는 것이 절대 좋은 경험은 아닐 것입니다.
따라서 저는 효율적으로 key를 관리할 수 있는 방법에 대해서 고민하게 되었습니다.
https://tkdodo.eu/blog/effective-react-query-keys
React Query 공부를 하던 중 마침 query key 관리법이 있어 읽어보게 되었습니다.
서비스의 확장에 따른 효과적인 keyFactory를 통해 키를 관리 전략을 소개하고 있었습니다.
간단하게 요약하자면, 공통의 부모 폴더에 하나의 객체로 선언하게 된다면 유지 보수 및 해당 query key 파악이 보다 효율적일 것입니다.

아래 코드와 같이 어떠한 기능과 관련된 query key들을 하나의 객체로 모아두었습니다
export const alarmKeys = {
list: ['alarm-list'] as const,
detail: (id: number) => [...alarmKeys.list, id] as const,
};
이렇게 된다면 유연하고 독립적으로 키에 접근이 가능할뿐더러 세부적인 key를 선언할 때 참고 및 변경이 용이합니다.
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { postReadAlarm, postReadAllAlarm } from '@/apis/alarm';
import { alarmKeys } from '@/hooks/query/keyFactory/alarmKeys';
function useAlarmMutation() {
const queryClient = useQueryClient();
const readAlarm = useMutation({
mutationFn: postReadAlarm,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: alarmKeys.list });
},
});
const readAllAlarm = useMutation({
mutationFn: postReadAllAlarm,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: alarmKeys.list });
},
});
return { readAlarm, readAllAlarm };
}
export default useAlarmMutation;
위와 같은 방법으로 query key 관리가 이루어지니, key를 찾기 위해 여기저기 파일을 돌아다닐 필요도 없어졌을뿐더러, 새로운 키를 선언할 때에도 고민을 하거나 기존 key를 찾는 시간이 불필요해졌습니다.
위의 방식은 정답이 아닌 한 가지의 전략입니다!
제가 생각했을 때 현재의 상황의 문제점이라고 느낀 부분을 해결하기 위해 택한 한 가지의 방법입니다.
상황이 다르고, 견해가 다르다면 좋지 않은 방법일 수 있습니다!
(이런 방법도 있구나~라고 생각해 주시면 감사하겠습니다)
각자의 도메인 성향과 처한 상황에서의 최선을 택하는 것이 각각의 정답이라 생각합니다.
읽어 주셔서 감사합니다!