🔥 쿼리 이해하기

544자
7분

쿼리의 기본

쿼리는 고유한 키와 연결된 비동기 데이터 소스에 대한 선언적 의존성입니다. 서버에서 데이터를 가져오기 위해 프로미스 기반의 모든 메서드(GET과 POST 포함)와 함께 쿼리를 사용할 수 있습니다. 서버의 데이터를 수정하는 메서드라면 쿼리 대신 뮤테이션을 사용하는 것이 좋습니다.

컴포넌트나 커스텀 훅에서 쿼리를 구독하려면 useQuery 훅을 호출할 때 최소한 다음 두 가지를 제공해야 합니다:

  • 쿼리의 고유한 키
  • 다음 중 하나를 반환하는 프로미스 함수:
    • 데이터를 반환하거나
    • 오류를 던집니다
import { useQuery } from '@tanstack/react-query'
 
function App() {
  const info = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
}
 
typescript

제공한 고유 키는 내부적으로 쿼리의 재조회, 캐싱, 애플리케이션 전체에서 쿼리 공유에 사용됩니다.

useQuery가 반환하는 쿼리 결과에는 템플릿 작성과 데이터 사용에 필요한 모든 정보가 들어 있습니다:

const result = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
 
typescript

결과 객체에는 생산성을 높이는 데 필요한 중요한 상태들이 포함되어 있습니다. 쿼리는 주어진 시점에 다음 상태 중 하나만 가질 수 있습니다:

  • isPending 또는 status === 'pending' - 쿼리에 아직 데이터가 없습니다.
  • isError 또는 status === 'error' - 쿼리에서 오류가 발생했습니다.
  • isSuccess 또는 status === 'success' - 쿼리가 성공했고 데이터를 사용할 수 있습니다.

이러한 주요 상태 외에도 쿼리의 상태에 따라 더 많은 정보를 얻을 수 있습니다:

  • error - 쿼리가 isError 상태일 때 error 속성을 통해 오류에 접근할 수 있습니다.
  • data - 쿼리가 isSuccess 상태일 때 data 속성을 통해 데이터에 접근할 수 있습니다.
  • isFetching - 쿼리가 어떤 상태에 있든 데이터를 가져오는 중이라면(백그라운드 재조회 포함) isFetching이 true가 됩니다.

대부분의 쿼리에서는 isPending 상태를 확인하고, 그 다음 isError 상태를 확인한 후, 마지막으로 데이터를 사용할 수 있다고 가정하고 성공 상태를 렌더링하는 것으로 충분합니다:

function Todos() {
  const { isPending, isError, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodoList,
  })
 
  if (isPending) {
    return <span>로딩 ...</span>
  }
 
  if (isError) {
    return <span>오류: {error.message}</span>
  }
 
  // 여기까지 왔다면 `isSuccess === true`라고 가정할 수 있습니다
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}
 
typescript

불리언 값을 선호하지 않는다면 status 상태를 사용할 수도 있습니다:

function Todos() {
  const { status, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodoList,
  })
 
  if (status === 'pending') {
    return <span>로딩 ...</span>
  }
 
  if (status === 'error') {
    return <span>오류: {error.message}</span>
  }
 
  // status === 'success'이지만, "else" 로직으로도 충분합니다
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}
 
typescript

TypeScript를 사용한다면 pending과 error 상태를 확인한 후 data에 접근할 때 타입을 올바르게 좁혀줍니다.

fetchStatus 이해하기

status 필드 외에도 fetchStatus 속성을 통해 추가적인 정보를 얻을 수 있습니다:

  • fetchStatus === 'fetching' - 쿼리가 현재 데이터를 가져오는 중입니다.
  • fetchStatus === 'paused' - 쿼리가 데이터를 가져오려 했지만 일시 중지되었습니다. 자세한 내용은 네트워크 모드 가이드를 참조하세요.
  • fetchStatus === 'idle' - 쿼리가 현재 아무 작업도 하고 있지 않습니다.

두 가지 상태가 필요한 이유

백그라운드 재조회와 stale-while-revalidate 로직으로 인해 status와 fetchStatus의 모든 조합이 가능합니다. 예를 들면:

  • 성공 상태의 쿼리는 보통 idle fetchStatus를 가지지만, 백그라운드 재조회가 진행 중이라면 fetching 상태일 수 있습니다.
  • 마운트되고 데이터가 없는 쿼리는 보통 pending 상태와 fetching fetchStatus를 가지지만, 네트워크 연결이 없다면 paused 상태일 수 있습니다.

따라서 쿼리가 실제로 데이터를 가져오지 않더라도 pending 상태일 수 있다는 점을 명심하세요. 간단히 말해:

  • status는 데이터에 대한 정보를 제공합니다: 데이터가 있는지 여부를 알려줍니다.
  • fetchStatus는 queryFn에 대한 정보를 제공합니다: 실행 중인지 여부를 알려줍니다.

더 알아보기

상태 확인을 수행하는 다른 방법을 알고 싶다면 커뮤니티 리소스를 참조하세요.