이 글은 TkDodo의 Seeding the Query Cache 를 번역한 글입니다.
지난 주 Promises의 일급 객체 지원에 관한 새로운 RFC 가 발표되었고, 이를 잘못 사용할 경우 어떻게 페치 워터폴이 발생할 수 있는지에 대한 논의가 있었습니다. 그렇다면 fetch waterfalls
이란 정확히 무엇일까요?
Fetch waterfalls
Waterfall은 하나의 요청이 이루어지고 다른 요청을 실행하기 전에 완료될 때까지 기다리는 상황을 설명합니다.
첫 번째 요청에는 두 번째 요청을 수행하는 데 필요한 정보가 포함되어 있기 때문에 불가피한 경우도 있습니다. 이를 종속 쿼리라고도 합니다.
그러나 대부분의 경우 독립적인 데이터이기 때문에 실제로 필요한 모든 데이터를 병렬로 가져올 수 있습니다.
React Query에서는 두 가지 방법으로 이를 수행할 수 있습니다.
두 변형 모두에서 React Query는 병렬로 데이터 가져오기를 시작합니다. 그렇다면 폭포는 어디에서 오는 것일까요?
Suspense
위에 링크된 RFC에서 설명한 것처럼, 서스펜스는 React로 프로미스를 풀어주는 방법입니다. 프로미스의 특징은 pending
, fulfilled
또는 rejected
된 세 가지 상태가 될 수 있다는 것입니다.
컴포넌트를 렌더링할 때 우리는 대부분 성공 시나리오에 관심이 있습니다. 모든 컴포넌트의 로딩 및 오류 상태를 처리하는 것은 지루할 수 있으며, 서스펜스는 이 문제를 해결하는 데 목적이 있습니다.
promise가 pending
일 때 React는 컴포넌트 트리를 마운트 해제하고 Suspense
컴포넌트에 의해 정의된 폴백을 렌더링합니다.
오류가 발생하는 경우 오류는 가장 가까운 ErrorBoundary
까지 버블링됩니다.
이렇게 하면 컴포넌트가 이러한 상태를 처리하지 않게 되고, 우리는 행복하게 경로에 집중할 수 있습니다. 거의 캐시에서 값을 읽는 동기식 코드처럼 작동합니다. React Query는 v5부터 이를 위한 전용 useSuspenseQuery
훅을 제공합니다:
Suspense waterfalls
그래서 이것은 훌륭하고 모든 것이 좋습니다. 하지만, 서스펜스가 켜져 있는 동일한 컴포넌트에서 여러 쿼리를 사용하면 역효과를 낼 수 있습니다. 이런 일이 발생합니다:
- 구성요소가 렌더링되고 첫 번째 쿼리를 읽으려고 시도합니다.
- 캐시에 아직 데이터가 없으므로 일시 중단됩니다.
- 이렇게 하면 구성 요소 트리가 마운트 해제되고 폴백이 렌더링됩니다.
- 가져오기가 완료되면 구성 요소 트리가 다시 마운트됩니다.
- 이제 첫 번째 쿼리를 캐시에서 성공적으로 읽었습니다.
- 구성 요소는 두 번째 쿼리를 보고 읽으려고 시도합니다.
- 두 번째 쿼리에는 캐시에 데이터가 없으므로 (다시) 일시 중단됩니다.
- 두 번째 쿼리를 가져옵니다.
- 구성요소가 마침내 성공적으로 렌더링 됩니다.
fallback
이 필요 이상으로 오래 유지되기 때문에 애플리케이션의 성능에 상당한 영향을 미칩니다.
이 문제를 피하는 가장 좋은 방법은 구성 요소당 하나의 쿼리를 고수하거나 구성 요소가 데이터를 읽으려고 할 때 캐시에 이미 데이터가 있는지 확인하는 것입니다.
Prefetching
fetch
를 빨리 시작할수록 더 빨리 완료할 수 있으므로 빨리 시작할수록 좋습니다. 🤓
- 아키텍처가 서버 측 렌더링을 지원하는 경우 서버에서 가져오는 것이 좋습니다.
- 로더를 지원하는 라우터가 있는 경우 해당 라우터에서 프리페치를 고려해 보십시오 .
그러나 그렇지 않은 경우에도 prefetchQuery
를 사용하여 구성 요소가 렌더링되기 전에 가져오기를 시작할 수 있습니다.
prefetchQuery
호출은 JavaScript 번들이 평가되는 즉시 실행됩니다. 이는 사용자가 해당 페이지로 이동하자마자 특정 페이지의 코드가 느리게 로드되고 평가된다는 것을 의미하기 때문에 기본 코드 분할을 라우팅 하는 경우 매우 잘 작동합니다.
즉, 구성 요소가 렌더링되기 전에 계속 시작됩니다. 이 예제에서 두 쿼리 모두에 대해 이 작업을 수행하면 suspense를 사용하는 경우에도 해당 병렬 쿼리를 다시 가져올 수 있습니다.
보시다시피 쿼리는 둘 다 가져오기가 완료될 때까지 계속 일시 중단되지만 병렬로 트리거했기 때문에 이제 대기 시간이 크게 단축되었습니다.
Update v5에는 모든 가져오기를 병렬로 트리거하는 전용
useSuspenseQueries
훅이 있습니다.
The use RFC
저는 아직 RFC에 대해 충분히 알지 못해서 제대로 논평할 수 없습니다. 캐시 API가 어떻게 작동할 것인지와 같은 큰 부분이 아직 빠져 있습니다. 개발자가 초기에 명시적으로 캐시를 시드하지 않으면 기본 동작으로 인해 워터폴이 발생한다는 점은 약간 문제가 있다고 생각합니다. 그래도 React Query의 내부를 이해하고 유지 관리하기가 더 쉬워질 것 같아서 꽤 기대가 됩니다. 사용자 환경에서 많이 사용될 수 있을지는 아직 지켜봐야 합니다.
Seeding details from lists
캐시를 읽을 때 캐시가 채워지도록 하는 또 다른 좋은 방법은 캐시의 다른 부분에서 캐시를 시드하는 것입니다. 항목의 상세 보기를 렌더링하는 경우 이전에 항목 목록을 표시하는 목록 보기를 사용했다면 해당 항목에 대한 데이터를 바로 사용할 수 있는 경우가 종종 있습니다.
목록 캐시의 데이터로 세부 정보 캐시를 채우는 두 가지 일반적인 방법이 있습니다.
Pull approach
이것은 문서 에도 설명되어 있습니다: 상세 보기를 렌더링하려고 할 때 렌더링하려는 항목의 목록 캐시를 조회합니다. 목록 캐시가 있으면 세부 정보 쿼리의 초기 데이터로 사용합니다.
initialData
함수가 undefined
를 반환하면 쿼리는 정상적으로 진행되고 서버에서 데이터를 가져옵니다. 그리고 무언가가 발견되면 캐시에 직접 저장됩니다.
staleTime
을 설정한 경우 초기 데이터가 최신 데이터로 간주되므로 더 이상의 백그라운드 새로 고침이 발생하지 않는다는 점에 유의하세요. 마지막으로 목록을 가져온 시간이 20분 전이라면 원하는 결과가 아닐 수도 있습니다.
문서 에 표시된 것처럼, 세부 쿼리에서 initialDataUpdatedAt
을 추가로 지정할 수 있습니다. 이렇게 하면 initialData
로 전달한 데이터가 원래 언제 가져왔는지 React Query에 알려주므로 유효기간을 올바르게 판단할 수 있습니다. 편리하게도 React Query는 목록이 언제 마지막으로 가져왔는지도 알고 있으므로 이를 전달하기만 하면 됩니다:
🟢 캐시를 “Just in Time”으로 시드합니다.
🔴 stale하지 않은 상태를 고려하기 위해 더 많은 작업이 필요합니다.
Push approach
또는 목록 쿼리를 가져올 때마다 세부 정보 캐시를 만들 수 있습니다. 이 방법은 세부 정보 항목을 생성할 때 목록을 가져온 시점부터 자동으로 유효 기간을 측정할 수 있다는 장점이 있습니다.
그러나 쿼리를 가져올 때 연결할 수 있는 좋은 콜백이 없습니다. 캐시 자체의 글로벌 onSuccess
콜백은 작동할 수 있지만 모든 쿼리에 대해 실행되므로 올바른 쿼리 키로 범위를 좁혀야 합니다.
푸시 접근 방식을 실행하는 가장 좋은 방법은 데이터를 가져온 후 queryFn
에서 직접 수행하는 것입니다.
이렇게 하면 목록의 각 항목에 대한 세부정보 항목이 즉시 생성됩니다. 현재 해당 쿼리에 관심이 있는 사람이 없으므로 비활성 상태로 표시되며, 이는 gcTime
(기본값: 15분)이 경과한 후에 가비지 수집될 수 있음을 의미합니다.
따라서 푸시 방식을 사용하는 경우 사용자가 실제로 세부 정보 보기로 이동한 후에는 여기에서 만든 세부 정보 항목을 더 이상 사용할 수 없게 될 수 있습니다. 또한 목록이 길면 필요하지도 않은 항목을 너무 많이 만들 수도 있습니다.
🟢 staleTime은 자동으로 적용됩니다.
🟡 좋은 콜백이 없습니다.
🟡 불필요한 캐시 항목을 만들 수 있습니다.
🔴 푸시된 데이터가 너무 일찍 gc될 수 있습니다.
두 방법 모두 세부 정보 쿼리의 구조가 목록 쿼리의 구조와 정확히 동일한(또는 최소한 할당 가능한) 경우에만 잘 작동한다는 점을 명심하십시오. 세부 정보 보기에 목록에 없는 필수 필드가 있는 경우 initialData
를 통해 시드하는 것은 좋은 생각이 아닙니다.
이것은 placeholderData
가 들어오는 곳이며, # 9 : Placeholder 및 Initial Data in React Query 에서 두 가지에 대한 비교를 작성했습니다.