현재 쿼리 키 관리의 Pain Points

  1. 매번 달라지는 쿼리 함수

    API 로직/옵션이 여기저기 흩어져 있어 동일 도메인에서도 queryFn, staleTime, refetch*가 일관되지 않게 섞입니다. 디버깅도 힘들고, 개발자마다 스타일이 달라져 응집도가 낮아집니다.

    스크린샷 2025-09-13 오후 5.15.59.png

  2. 일관성 떨어지는 쿼리 키 네이밍

    같은 데이터를 다루지만 다른 네이밍 ['myInfo'], [myData], 'GET_AVAILABLE_TIMES_BY_GROUP' 등 표현식과 구조가 제각각입니다. 어떤 키가 어떤 스코프에 속하는지, 무효화(invalidate) 범위는 어떻게 잡아야 하는지 매번 추측해야 합니다.

    스크린샷 2025-09-13 오후 5.17.30.png

    스크린샷 2025-09-13 오후 5.17.22.png

  3. 중복 선언/중복 로직

    유사한 키/함수가 여러 파일에 반복 선언됩니다. 한 번 바꾸려면 여러 군데를 동시에 수정해야 하고, 버그가 숨어들기 쉽습니다.

  4. 일관성 떨어지는 쿼리키 네이밍

그래서 생각했던 대안

A. 키/함수 동시 팩토리

export const checklistKeys = {
  all: ['checklists'] as const,
  lists: (filters: object = {}) => [...checklistKeys.all, 'list', filters] as const,
  mine: (userId: string) => [...checklistKeys.lists(), 'mine', userId] as const,
  completed: (filters: object) => [...checklistKeys.lists(), 'completed', filters] as const,
  pending: (filters: object) => [...checklistKeys.lists(), 'pending', filters] as const,
  detail: (id: number) => [...checklistKeys.all, 'detail', id] as const,
  items: (id: number) => [...checklistKeys.detail(id), 'items'] as const,
};

B. 키만 별도 상수로 관리

export const QUERY_KEYS = {
  CHECKLIST: {
    GET_CHECKLIST_INFO: 'get-checklist-info',
    GET_MY_CHECKLISTS: 'get-my-checklists',
    GET_COMPLETED_CHECKLISTS: 'get-completed-checklists',
    GET_PENDING_CHECKLISTS: 'get-pending-checklists',
  },
  MEMO: {
    GET_MEMO_LIST: 'get-memo-list',
    GET_MEMO_DETAIL: 'get-memo-detail',
    GET_FAVORITE_MEMOS: 'get-favorite-memos',
    GET_ARCHIVED_MEMOS: 'get-archived-memos',
    GET_SHARED_MEMOS: 'get-shared-memos',
  },
};

C. TanStack queryOptions만으로 순수 팩토리

import { queryOptions } from '@tanstack/react-query';

const fetchTodos = async (filters: string) => { /* ... */ };
const fetchTodo  = async (id: number) => { /* ... */ };

export const todoQueries = {
  all:   () => ['todos'] as const,
  lists: () => [...todoQueries.all(), 'list'] as const,
  list:  (filters: string) =>
    queryOptions({
      queryKey: [...todoQueries.lists(), { filters }] as const,
      queryFn: () => fetchTodos(filters),
      staleTime: 5_000,
    }),

  details: () => [...todoQueries.all(), 'detail'] as const,
  detail:  (id: number) =>
    queryOptions({
      queryKey: [...todoQueries.details(), id] as const,
      queryFn: () => fetchTodo(id),
      staleTime: 5_000,
    }),
};