🔥 초기 쿼리 데이터

769자
9분

쿼리 캐시에 필요한 데이터를 미리 준비하는 방법은 여러 가지가 있습니다. 이 방법들을 크게 선언적 방식과 명령적 방식으로 나눌 수 있습니다.

선언적 방식:

  • 쿼리에 initialData를 제공하여 캐시가 비어있을 때 미리 데이터를 채웁니다.

명령적 방식:

initialData로 쿼리 미리 채우기

때로는 앱에서 쿼리에 필요한 초기 데이터를 이미 가지고 있을 수 있습니다. 이런 경우 config.initialData 옵션을 사용하면 쿼리의 초기 데이터를 설정하고 처음 로딩 상태를 건너뛸 수 있습니다.

주의: initialData는 캐시에 저장되므로, 이 옵션에 임시나 부분적인 데이터를 제공하는 것은 좋지 않습니다. 대신 placeholderData를 사용하세요.

const result = useQuery({
  queryKey: ['todos'],
  queryFn: () => fetch('/todos'),
  initialData: initialTodos,
})
 
typescript

staleTime과 initialDataUpdatedAt

기본적으로 initialData는 방금 가져온 것처럼 완전히 새로운 데이터로 취급됩니다. 이는 staleTime 옵션의 해석에도 영향을 미칩니다.

  • initialData를 설정하고 staleTime을 지정하지 않으면(기본값 staleTime: 0), 쿼리는 마운트 직후 즉시 데이터를 다시 가져옵니다:
// initialTodos를 즉시 보여주지만, 마운트 직후 todos를 다시 가져옵니다
const result = useQuery({
  queryKey: ['todos'],
  queryFn: () => fetch('/todos'),
  initialData: initialTodos,
})
 
typescript
  • initialData와 함께 staleTime을 1000ms로 설정하면, 데이터는 쿼리 함수에서 방금 가져온 것처럼 취급됩니다. 즉, 설정한 staleTime 동안(이 경우 1000ms) 데이터를 신선한 상태로 간주합니다. 이 기간 동안 쿼리는 데이터를 다시 가져오지 않습니다.:
// initialTodos를 즉시 보여주고, 1000ms 후 다음 상호작용이 있을 때까지 다시 가져오지 않습니다
const result = useQuery({
  queryKey: ['todos'],
  queryFn: () => fetch('/todos'),
  initialData: initialTodos,
  staleTime: 1000,
})
 
typescript
  • initialData가 완전히 새로운 것이 아니라면 어떻게 해야 할까요? 이 경우 initialDataUpdatedAt 옵션을 사용할 수 있습니다. 이 옵션을 통해 initialData가 마지막으로 업데이트된 시간을 밀리초 단위의 JS 타임스탬프로 전달할 수 있습니다. Date.now()가 제공하는 값과 같습니다. 유닉스 타임스탬프를 사용한다면 1000을 곱해 JS 타임스탬프로 변환해야 합니다.
// initialTodos를 즉시 보여주고, 1분 후 다음 상호작용이 있을 때까지 다시 가져오지 않습니다
const result = useQuery({
  queryKey: ['todos'],
  queryFn: () => fetch('/todos'),
  initialData: initialTodos,
  staleTime: 60 * 1000, // 1분
  // 이 값은 10초 전이나 10분 전일 수 있습니다
  initialDataUpdatedAt: initialTodosUpdatedTimestamp, // 예: 1608412420052
})
 
typescript

이 옵션을 사용하면 staleTime을 원래 목적대로 데이터의 신선도를 결정하는 데 사용할 수 있습니다. 동시에 initialDatastaleTime보다 오래되었다면 마운트 시 데이터를 다시 가져올 수 있습니다. 위 예제에서는 데이터가 1분 이내로 새로워야 하며, initialData가 마지막으로 업데이트된 시간을 쿼리에 알려줍니다. 이를 통해 쿼리는 데이터를 다시 가져올지 여부를 스스로 결정할 수 있습니다.

데이터를 미리 가져온 것처럼 취급하고 싶다면, prefetchQuery나 fetchQuery API를 사용하여 미리 캐시를 채우는 것이 좋습니다. 이렇게 하면 staleTime을 initialData와 별개로 설정할 수 있습니다.

초기 데이터 함수

쿼리의 초기 데이터에 접근하는 과정이 복잡하거나 매 렌더링마다 수행하고 싶지 않다면, initialData 값으로 함수를 전달할 수 있습니다. 이 함수는 쿼리가 초기화될 때 한 번만 실행되어 메모리나 CPU 자원을 절약할 수 있습니다:

const result = useQuery({
  queryKey: ['todos'],
  queryFn: () => fetch('/todos'),
  initialData: () => getExpensiveTodos(),
})
 
typescript

캐시에서 초기 데이터 가져오기

때로는 다른 쿼리의 캐시된 결과에서 쿼리의 초기 데이터를 제공할 수 있습니다. 예를 들어, 할 일 목록 쿼리의 캐시된 데이터에서 개별 할 일 항목을 검색하여 개별 할 일 쿼리의 초기 데이터로 사용할 수 있습니다:

const result = useQuery({
  queryKey: ['todo', todoId],
  queryFn: () => fetch('/todos'),
  initialData: () => {
    // 'todos' 쿼리의 할 일을 이 할 일 쿼리의 초기 데이터로 사용합니다
    return queryClient.getQueryData(['todos'])?.find((d) => d.id === todoId)
  },
})
 
typescript

initialDataUpdatedAt과 함께 캐시에서 초기 데이터 가져오기

캐시에서 초기 데이터를 가져올 때는 그 데이터가 이미 오래되었을 가능성이 높습니다. 이런 상황에서 임의의 staleTime을 설정하여 쿼리가 즉시 데이터를 다시 가져오는 것을 막는 대신, 더 스마트한 방법을 사용할 수 있습니다. 바로 원본 데이터 소스 쿼리의 dataUpdatedAt 값을 initialDataUpdatedAt에 전달하는 것입니다. 이렇게 하면 쿼리는 초기 데이터가 언제 마지막으로 업데이트되었는지 정확히 알 수 있습니다. 이 정보를 바탕으로 쿼리는 초기 데이터의 실제 나이와 설정된 staleTime을 모두 고려하여, 언제 데이터를 다시 가져와야 할지 더 현명하게 판단할 수 있습니다. 결과적으로 쿼리는 데이터의 신선도를 정확히 파악하고 필요한 시점에 효율적으로 데이터를 갱신할 수 있게 됩니다.

const result = useQuery({
  queryKey: ['todos', todoId],
  queryFn: () => fetch(`/todos/${todoId}`),
  initialData: () =>
    queryClient.getQueryData(['todos'])?.find((d) => d.id === todoId),
  initialDataUpdatedAt: () =>
    queryClient.getQueryState(['todos'])?.dataUpdatedAt,
})
 
typescript

조건부로 캐시에서 초기 데이터 가져오기

초기 데이터의 출처인 쿼리가 오래되었다면 캐시된 데이터를 전혀 사용하지 않고 서버에서 직접 가져오고 싶을 수 있습니다. 이런 결정을 쉽게 내리기 위해 queryClient.getQueryState 메서드를 사용하여 출처 쿼리에 대한 더 많은 정보를 얻을 수 있습니다. 여기에는 쿼리가 "충분히 새로운지" 판단할 수 있는 state.dataUpdatedAt 타임스탬프가 포함됩니다:

const result = useQuery({
  queryKey: ['todo', todoId],
  queryFn: () => fetch(`/todos/${todoId}`),
  initialData: () => {
    // 쿼리 상태를 가져옵니다
    const state = queryClient.getQueryState(['todos'])
 
    // 쿼리가 존재하고 데이터가 10초보다 오래되지 않았다면...
    if (state && Date.now() - state.dataUpdatedAt <= 10 * 1000) {
      // 개별 할 일을 반환합니다
      return state.data.find((d) => d.id === todoId)
    }
 
    // 그렇지 않다면 undefined를 반환하고 완전한 로딩 상태에서 가져오게 합니다!
  },
})
 
typescript

더 읽을거리

초기 데이터와 placeholder 데이터의 비교에 대해서는 커뮤니티 리소스를 참조하세요.