React Query useQuery 사용방법

이재철·2023년 3월 1일
2

react-query

목록 보기
4/12
post-thumbnail

정의

useQuery hook 는 서버 데이터를 GET 요청할 때 사용되는 hook

  • 기본적으로 비동기 방식으로 동작
  • GET 요청에 대한 쿼리와 요청에 대한 서버 데이터를 캐싱하기 위해 사용합니다.
import { useQuery } from '@tanstack/react-query';

const { data, isLoading, error } = useQuery(queryKey, queryFn, Options);

Parameters

  1. queryKey : 첫번째 인수는 ['key', ...deps] 배열을 주입합니다.
    • 쿼리를 식별할 수 있는 고유한 키 값을 문자열을 사용함
    • 두 번째 요소 이후는 콜백함수 내부에서 의존하고 있는 외부 식별자를 작성
    • querykey 값이 같더라도 의존하고 있는 식별자 값이다른 경우 다른 쿼리로 식별하며, 별도의 쿼리로 캐싱하여 관리합니다.
  2. queryFn : 데이터를 fetching하는 비동기 함수를 전달하며, Promise 객체를 반환하는 함수를 전달합니다.
  3. options : 객체 타입으로 쿼리에 대한 옵션을 작성할 수 있습니다.

    참조 : TanStack Query v4 - useQuery

React Query and TypeScript

Generics

react query는 Generic 을 많이 사용합니다.

  • 라이브러리가 실제로 데이터를 가져오지 않고, API가 반환하는 데이터 타입을 알 수 없기 때문입니다.
function useGroups() {
  return useQuery<Group[], Error>('groups', fetchGroups)
}

The four Generics

useQuery Hook의 정의

export function useQuery<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey
>
KeyValue
TQueryFnDataqueryFn 반환되는 타입 (ex Group[])
TErrorqueryFn에서 예상되는 오류 타입 (ex Error)
TData데이터 프로퍼티가 최종적으로 보유되는 타입,
queryFn이 반환하는 것과 다를 수 있으므로 선택 옵션을 사용하는 경우 해당
default : queryFn
TQueryKeyqueryFn에 전달된 queryKey를 사용하는 경우 해당 queryKey 타입

Partial Type Argument Inference

하나의 Generic 을 제공하는 경우 모든 Generic 을 제공해야 합니다.

예시

  • 세번 째 Generic 을 제공하지 않았기 때문에 기본 값은 Group[] 이지만 선택자 (select)에서 number를 반환하기 때문에 타입 에러가 발생합니다.
function useGroupCount() {
  return useQuery<Group[], Error>('groups', fetchGroups, {
    select: (groups) => groups.length,
// 🚨 Type '(groups: Group[]) => number' is not assignable to type '(data: Group[]) => Group[]'.
// Type 'number' is not assignable to type 'Group[]'.ts(2322)
  })
}

➡ 단순하게 해결하기 위해서는 세번 째 제네릭을 추가합니다.

function useGroupCount() {
  // ✅ fixed it
  return useQuery<Group[], Error, number>('groups', fetchGroups, {
    select: (groups) => groups.length,
  })
}

Infer all the things

장점

  • Generic 을 수동으로 지정하지 않습니다.
  • 세 번째(select) 및 네 번째(쿼리 키) 제네릭이 필요한 경우에 동작합니다.
  • 코드가 덜 혼란스럽고, 자바스크립트에 더 가깝습니다.
function fetchGroups(): Promise<Group[]> {
  return axios.get('groups').then((response) => response.data)
}

// ✅ data will be `Group[] | undefined` here
function useGroups() {
  return useQuery(['groups'], fetchGroups)
}

// ✅ data will be `number | undefined` here
function useGroupCount() {
  return useQuery(['groups'], fetchGroups, {
    select: (groups) => groups.length,
  })
}

What about error?

Generic 을 사용하지 않으면 오류가 unknown 으로 추론됩니다.
➡ 자바스크립트에서는 무엇이든 throw 할 수 있으며 Error 타입이 아니어도 됩니다.

react query는 Promise를 반환하는 기능을 담당하지 않기 때문에 어떤 타입의 오류가 발생하는지 알 수 없기 때문에 unknown 타입으로 반환됩니다.
💡 검사 인스턴스를 통해 타입을 좁힐 수 있습니다.

const groups = useGroups()

if (groups.error) {
  // 🚨 this doesn't work because: Object is of type 'unknown'.ts(2571)
  return <div>An error occurred: {groups.error.message}</div>
}

// ✅ the instanceOf check narrows to type `Error`
if (groups.error instanceof Error) {
  return <div>An error occurred: {groups.error.message}</div>
}

Type Narrowing

data 또는 error와 같은 이름은 매우 보편적으로 수정할 가능성이 높음
➡ destructuring을 사용하지 않음

const { data, isSuccess } = useGroups()
if (isSuccess) {
  // 🚨 data will still be `Group[] | undefined` here
}

const groupsQuery = useGroups()
if (groupsQuery.isSuccess) {
  // ✅ groupsQuery.data will now be `Group[]`
}

Type safety with the enabled option

enabled 옵션을 사용하면 useQuery를 동기적으로 사용할 수 있습니다.

id의 값이 정의되지 않을 수 있음 (undefined)
enabled 옵션은 어떤 타입 좁힙도 수행하지 않음

function fetchGroup(id: number): Promise<Group> {
  return axios.get(`group/${id}`).then((response) => response.data)
}

function useGroup(id: number | undefined) {
  return useQuery(['group', id], () => fetchGroup(id), {
    enabled: Boolean(id),
  })
  // 🚨 Argument of type 'number | undefined' is not assignable to parameter of type 'number'.
  //  Type 'undefined' is not assignable to type 'number'.ts(2345)
}

💡 queryFn 에서 Promise를 reject하는 것이 가장 좋은 방법
중복되지만 명확하며 안전합니다.

function fetchGroup(id: number | undefined): Promise<Group> {
  // ✅ check id at runtime because it can be `undefined`
  return typeof id === 'undefined'
    ? Promise.reject(new Error('Invalid id'))
    : axios.get(`group/${id}`).then((response) => response.data)
}

function useGroup(id: number | undefined) {
  return useQuery(['group', id], () => fetchGroup(id), {
    enabled: Boolean(id),
  })
}

참조

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

0개의 댓글