React Query queryKey 관리하기

이재철·2023년 2월 26일
3

react-query

목록 보기
3/12
post-thumbnail

queryKey

queryKey는 편의상 Key라고 지칭했습니다.

Cashing Data

  • 내부에서 query cache는 Key가 직렬화되어 있으며, 해쉬되어 관리됩니다.

오브젝트의 키 순서와 관계없이 다음쿼리키는 모두 같은 쿼리로 취급

useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)

다음 쿼리 키는 같지 않음

useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
  • Key 는 쿼리에 대해 유니크 값
  • React Query는 cache에 Key를 이용해 접근
  • useQueryuseInfiniteQuery에 동일한 Key를 사용할 수 없음 ➡ 하나의 query cache만 유효
useQuery(['todos'], fetchTodos)

// 🚨 잘못된 사용 (query key 중복)
useInfiniteQuery(['todos'], fetchInfiniteTodos)

// ✅ 사용 가능
useInfiniteQuery(['infiniteTodos'], fetchInfiniteTodos)

배열로 키 관리

  • tanstak/queryKey는 배열로 선언함
// 🚨 will be transformed to ['todos'] anyhow
useQuery('todos')
// ✅
useQuery(['todos'])

Colocate

Kent C. Dodds의 Colocation을 통한 Maintenability

기능 디렉터리에 있는 해당 쿼리 옆에 쿼리 키를 보관해보자.

- src
 - features
  - Profile
   - index.tsx
   - queries.ts
  - Todos
   - index.tsx
   - queries.ts 

추천 패키지

lukemorales / query-key-factory

github : lukemorales/query-key-factory

특징

  • typescript 지원
  • 규칙성 있는 key 네이밍
  • 모든 키는 직렬화 가능한 객체가 있는 키를 포함
  • queryKeyqueryFn을 함께 선언하고 쿼리를 실행하는데 간단하게 사용 가능

구조

표준화 된 키

  • 직렬화 가능한 객체가 있는 키를 포함, @tanstack/query 규칙을 따름
import { createQueryKeys } from "@lukemorales/query-key-factory";

export const todos = createQueryKeys('todos', {
  detail: (todoId: string) => [todoId],
  list: (filters: TodoFilters) => ({
    queryKey: [{ filters }],
  }),
});

// => createQueryKeys output:
// {
//   _def: ['todos'],
//   detail: (todoId: string) => {
//     queryKey: ['todos', 'todo', todoId],
//   },
//   list: (filters: TodoFilters) => {
//     queryKey: ['todos', 'list', { filters }],
//   },
// }

종속적인 쿼리 선언

  • 종속적이거나 상위 컨텍스트와 관련된 쿼리를 선언
export const users = createQueryKeys('users', {
  detail: (userId: string) => ({
    queryKey: [userId],
    queryFn: () => api.getUser(userId),
    contextQueries: {
      likes: {
        queryKey: null,
        queryFn: () => api.getUserLikes(userId),
      },
    },
  }),
});

// => createQueryKeys output:
// {
//   _def: ['users'],
//   detail: (userId: string) => {
//     queryKey: ['users', 'detail', userId],
//     queryFn: (ctx: QueryFunctionContext) => api.getUser(userId),
//     _ctx: {
//       likes: {
//         queryKey: ['users', 'detail, userId, 'likes'],
//         queryFn: (ctx: QueryFunctionContext) => api.getUserLikes(userId),
//       },
//     },
//   },
// }

export function useUserLikes(userId: string) {
  return useQuery(users.detail(userId)._ctx.likes);
};

Key 접근이 간단

  • Key 접근이 간단하며 모드 캐시를 편리하게 무효화 할 수 있음
users.detail({ status: 'completed' }).queryKey; // => ['users', 'detail', userId]
users.detail._def; // => ['users', 'detail']

기능별로 쿼리 키 선언 및 병합

  • 기능별로 키를 정의할 수 있으며, 병합하여 모든 Key에 접근할 수 있음
// queries/users.ts
export const users = createQueryKeys('users', {
  all: null,
  detail: (userId: string) => ({
    queryKey: [userId],
    queryFn: () => api.getUser(userId),
  }),
});

// queries/todos.ts
export const todos = createQueryKeys('todos', {
  detail: (todoId: string) => [todoId],
  list: (filters: TodoFilters) => ({
    queryKey: [{ filters }],
    queryFn: (ctx) => api.getTodos({ filters, page: ctx.pageParam }),
  }),
});

// queries/index.ts
export const queries = mergeQueryKeys(users, todos);

편리한 타입 선언

// queries/index.ts
import { mergeQueryKeys, inferQueryKeyStore } from "@lukemorales/query-key-factory";

import { users } from './users';
import { todos } from './todos';

export const queries = mergeQueryKeys(users, todos);

export type QueryKeys = inferQueryKeyStore<typeof queries>;

QueryFunctionContext 쉬운 사용

import type { QueryKeys } from "../queries";
// import type { TodosKeys } from "../queries/todos";

type TodosList = QueryKeys['todos']['list'];
// type TodosList = TodosKeys['list'];

const fetchTodos = async (ctx: QueryFunctionContext<TodosList['queryKey']>) => {
  const [, , { filters }] = ctx.queryKey;
  return api.getTodos({ filters, page: ctx.pageParam });
}

export function useTodos(filters: TodoFilters) {
  return useQuery({
    ...queries.todos.list(filters),
    queryFn: fetchTodos,
  });
};

독립적으로 액세스 할 수 있음

// 🕺 remove everything related to the todos feature
queryClient.removeQueries(queries.posts.getPosts(page, limit).queryKey)

// 🚀 invalidate all the lists
queryClient.invalidateQueries(queries.posts.getPosts(page, limit).queryKey)

// 🙌 prefetch a single todo
queryClient.prefetchQueries(queries.posts.getPosts(page, limit).queryKey, () => useGetPosts(page, limit))

참조 링크

profile
혼신의 힘을 다하다 🤷‍♂️

0개의 댓글