React Query 함수 컨텍스트 활용하기

이재철·2023년 3월 1일
0

react-query

목록 보기
7/12
post-thumbnail

Hot take

인라인 함수는 Custom Hook에서 사용할 수 있는 다른 변수를 닫을 수 있기에 queryFn에 전달하는 가장 쉬운 방법입니다.

type State = 'all' | 'open' | 'done'
type Todo = {
  id: number
  state: TodoState
}
type Todos = ReadonlyArray<Todo>

const fetchTodos = async (state: State): Promise<Todos> => {
  const response = await axios.get(`todos/${state}`)
  return response.data
}

export const useTodos = () => {
  // imagine this grabs the current user selection
  // from somewhere, e.g. the url
  const { state } = useTodoParams()

  // ✅ The queryFn is an inline function that
  // closures over the passed state
  return useQuery(['todos', state], () => fetchTodos(state))
}

🚨 매개변수가 많은 경우 상당한 문제를 발생할 수 있습니다.

type Sorting = 'dateCreated' | 'name'
const fetchTodos = async (
  state: State,
  sorting: Sorting
): Promise<Todos> => {
  const response = await axios.get(`todos/${state}?sorting=${sorting}`)
  return response.data
}

쿼리에 정렬을 추가하려고합니다.
➡ fetchTodos에서 Custom Hook에서 오류가 발생할 수 있습니다.
쿼리 키가 실제 종속성과 다를 수 있는 복잡한 문제가 발생할 수 있습니다.😡

export const useTodos = () => {
  const { state, sorting } = useTodoParams()

  // 🚨 can you spot the mistake ⬇️
  return useQuery(['todos', state], () => fetchTodos(state, sorting))
}

해결방법?
모든 종속성을 포함하여 쿼리 키를 생성하여 이 문제를 해결하는 babel-plugin-react-query-key-gen 있습니다.

💡 react query에는 종속성을 처리하는 다른 기본 메서드가 포함되어 있습니다.
QueryFunctionContext 사용하는 것입니다.

QueryFunctionContext

QueryFunctionContextqueryFn 에 인수로 전달되는 객체입니다.

const fetchTodos = async ({ queryKey }) => {
  // 🚀 we can get all params from the queryKey
  const [, state, sorting] = queryKey
  const response = await axios.get(`todos/${state}?sorting=${sorting}`)
  return response.data
}

export const useTodos = () => {
  const { state, sorting } = useTodoParams()

  // ✅ no need to pass parameters manually
  return useQuery(['todos', state, sorting], fetchTodos)
}

How to type the QueryFunctionContext

전체 타입의 안정성을 확보하고 useQuery에 전달된 queryKey에서 QueryFunctionContext 의 타입을 추론하는 것입니다.

export const useTodos = () => {
  const { state, sorting } = useTodoParams()

  return useQuery(
    ['todos', state, sorting] as const,
    async ({ queryKey }) => {
      const response = await axios.get(
        // ✅ this is safe because the queryKey is a tuple
        `todos/${queryKey[1]}?sorting=${queryKey[2]}`
      )
      return response.data
    }
  )
}

🚨 여전히 많은 단점이 있습니다.

  • 무언가를 사용하기 위해 클로저 내부에 쿼리를 생성해야 합니다.
  • queryKey 를 사용하여 위의 방법으로 URL을 작성하는 것은 모든 문자열을 지정할 수 있으므로 여전히 안전하지 않습니다.

Query Key Factories

키를 빌드하기 위한 typesafe query key factory 가 있는 경우 반환 타입을 사용하여 QueryFunctionContext 를 입력할 수 있도록 합니다.

참고 : react-query-querykey

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

export const postQueryKeys = createQueryKeys("posts", {
  getPosts: (page?: number, limit?: number) => ({
    queryKey: [{ page, limit }],
  }),
});

// postTypes.ts
export const queries = mergeQueryKeys(postQueryKeys);
export type QueryKeys = inferQueryKeyStore<typeof queries>;
export type GetPostListQuery = QueryKeys["posts"]["getPosts"];

export interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

// queries.ts
const getPostsApi = async (
  ctx: QueryFunctionContext<GetPostListQuery["queryKey"]>,
): Promise<Post[]> => {
  const [, , { page, limit }] = ctx.queryKey;
  const response = await axiosClient.get<Post[]>(
    "https://jsonplaceholder.typicode.com/posts",
    {
      params: { _page: page, _limit: limit },
    },
  );
  return response.data;
};

const useGetPosts = (page: number, limit: number) => {
  return useQuery(
    queries.posts.getPosts(page, limit).queryKey,
    getPostsApi,
  );
};

QueryFunctionContext 타입은 react query에 의해 export 됩니다.
queryKey의 타입을 정의하는 하나의 제네릭을 사용합니다.
해당 구조에 맞지 않는 키를 사용하면 타입 오류가 발생하게 됩니다.

참조

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

0개의 댓글