React Query는 React 앱의 서버 상태를 관리하는 라이브러리.
데이터관리
Client State
- 웹 브라우저 세션과 관련된 모든 정보를 의미 (서버에서 일어나는 일과 아무 관련이 없음)
- 단순 사용자의 상태를 추적
ex) 언어 선택, 다크 모드&라이트 모드
Server State
- 서버에 저장되는 정보. 클라이언트에 표시하는 데 필요한 데이터
ex) 데이터베이스에 저장하는 블로그 게시물 데이터
React query는 클라이언트단에서 서버 데이터 캐시를 관리
React 코드에 서버 데이터가 필요할 때 fetch나 axios를 사용해 서버로 바로 이동하지 않고 react qeury 캐시를 요청
데이터를 관리하는 것은 react query이지만 서버의 새 데이터로 캐시를 업데이트하는 시기를 설정하는 것은 사용자의 몫
key: 데이터가 식별되는 방식
클라이언트의 데이터와 서버의 데이터를 맞춰보는 방법 2가지가 있다.
1. 명령형 처리
- 쿼리 클라이언트에 해당 데이터를 무효화하고 캐시에 교체할 새 데이터를 서버에서 가져오게 지시하는 것
2. 선언형 처리
- refetch를 트리거하는 조건을 구성 ex) 브라우저 창이 다시 포커싱 되었을 때 처리하는 것 or staleTime으로 다시 가져오기를 언제 트리거할지 정할 수도 있음
데이터 관리 뿐 아니라 서버 상태 관리에 도움이 되는 많은 도구가 함께 제공
Loading / Error states
- 서버에 대한 모든 쿼리의 로딩 및 오류 상태를 유지해주기 때문에 수동으로 할 필요가 없어진다.
Pagination / infinite scroll
- 페이지네이셔이나 무한 스크롤이 필요한 경우 데이터를 조각으로 가져올 수 있는 도구도 제공
Prefetching
- 사용자가 언제 이를 필요로 할지 예상하여 prefetch를 수행할 수 있음.
- 데이터를 미리 가져와서 캐시에 넣으면 사용자에게 데이터가 필요할 때 앱이 캐시에서 해당 데이터를 가져오기 때문에 사용자는 서버에 연결할 때까지 기다릴 필요가 없어짐
ex) 1페이지 이동시, 2페이지의 내용까지 가져옴
Mutations
- 데이터 변이나 업데이트를 관리할 수 있음
De-duplication of requests
- 쿼리는 키로 식별되기 때문에 React query는 요청을 관리할 수 있고
- 페이지를 로드하고 해당 페이지의 여러 구성 요소가 동일한 데이터를 요청하는 경우 React query는 쿼리를 한 번에 보낼 수 있다.
- 기존 쿼리가 나가는 동안 다른 구성 요소가 데이터를 요청하는 경우 react query는 중복 요청을 제거할 수 있다.
Retry on error
- 오류가 발생하는 동안 재시도를 관리할 수 있음
Callbacks
- 쿼리가 성공하거나 오류가 났을 때를 구별해서 조취를 취할 수 있도록 콜백을 전달할 수도 있다.
QueryProvider
- Query Client를 속성으로 씀
const queryClient = new QueryClient();
...
<QueryClientProvider client={queryClient}>
<div className="App">
...
</div
</QueryClientProvider>
...
- 자식 컴포넌트에 캐시 및 클라이언트 구성을 제공하는 쿼리 공급자.
- 자식 컴포넌트가 react-query hook을 사용
프로바이더가 일종의 상자이고 모든 컴포가 상자 안의 것을 사용
const {data} = useQuery({
queryKey: ["posts"], // 쿼리 키는 캐시 내의 데이터를 정의, 항상 배열
queryFn: // 데이터를 가져오는 함수
})
isLoading vs isFetching
isFetching
- 캐시에 상관없이 함수가 실행 중이면 isFetching
- 비동기 쿼기라 아직 해결되지 않았다는 것을 말함. 아직 fetch가 완료되지 않았지만, axios 호출이나 GrapQL 호출 같은 다른 종류의 데이터를 가져오는 작업일 수 있다.
- isFetching은 어떠한 react-query요청 내부의 비동기 함수가 처리되었는지 여부 에 따라 true/false로 나누어 진다.
isLoading은 isFetching의 집합 안에 있음, 로딩 중을 의미
- isLoading은 캐시된 데이터조차 없이, 처음 실행된 쿼리 일 때 로딩 여부에 따라 true/false로 나누어 진다.
- 쿼리 함수가 아직 진행중이며넛 캐시된 데이터도 없음. 쿼리를 전에 실행한 적이 없어서 데이터를 가져오는 중이고 캐시된 데이터도 없어서 보여줄 수도 없다.
즉, 결론적으로 isLoading과 isFetching은 비슷하게 '로딩' 이라는 개념을 사용하지만 기존에 캐시된 데이터가 있느냐 에 따라 다르다.
- isLoading은 서버에 데이터 요청을 처음 할 때
- isFetching은 서버에 데이터 요청을 다시 할 때 (캐시된 데이터가 있을 때)
페이지네이션 등을 구현할 때 캐시된 데이터가 있는지 없는지 구분할 때 이 둘의 차이가 중요해짐
isLoading과 isFetching의 유일한 차이점은 캐시에 데이터가 있는지 없는지예요
staleTime vs gcTime
staleTime
stale: 데이터가 오래 되었다는 뜻
- 데이터가 만료괴었고 refetch할 준비가 되었음
- 데이터에 여전히 캐시가 있음
- 데이터 prefetch는 데이터가 stale일 때만 트리거 된다.
- 자동 데이터 prefetch 트리거
1) 쿼리를 포함하는 컴포넌트가 다시 마운트되거나
2) 브라우저 창이 리포키싱할 때
- stale 시간을 데이터의 최대 수명이라고 생각할 수 있음(서버로 부터 데이터를 가져오기 전 얼마나 살려둘 건지, 오래된 데이터를 얼마나 용인할건지 등)
- 기본적으로 staleTime은 항상 0 => 늘 데이터가 stale이라고 가정하고 서버에서 다시 가져와야 함을 말해줌.
예를들어 staleTime을 10,000으로 설정하였다면, data fetching이 성공한 후 10초(= 10,000ms) 동안 fresh 상태로 존재하다가 10초 이후에는 stale 상태가 됩니다.
즉,staleTime = 데이터를 얼마나 있다가 fresh 에서 stale로 상태를 변경할거냐
stale과 cache는 비슷한 역할을 하는 것 같은데 어째서 cache만으로 관리하지 않고, stale과 cache라는 개념으로 나누어서 관리하지? 라고 의문이 들었습니다. 그런데 cache만으로 관리했을 때와의 차이점을 생각해보니 알겠더라구요. 위 상황은 useQuery를 이용해 캐싱된 데이터가 stale 상태로 바뀐 이후에, 페이지를 이동하여 동일한 키값의 데이터를 refetching 해야하는 상황을 나타낸 것
차이점은 ⓒ입니다. 현재처럼 stale과 cache로 관리하게 된다면, ⓒ에서 stale 상태의 캐싱된 데이터를 이용한 UI를 먼저 보여준 후, refetching을 완료하면 새로운 데이터를 이용한 UI로 교체해줄 수 있습니다. refetching 이전과 이후의 데이터가 많이 다르지 않다면, 사용자는 페이지 이동 후 더욱 빠르게 서비스를 이용할 수 있게됩니다.
반면 cache만으로 관리하게 된다면 이와는 달라집니다. 위 그래프에서 페이지 이동 직전에 캐시가 만료되었다고 가정해보겠습니다. 캐시가 만료되어 삭제되었을테니, ⓒ에서 refetching이 완료되기 전까지 데이터가 없으므로 Loading 페이지를 보여줘야합니다. 그리고 refetching이 완료되면 그제야 데이터를 이용한 UI를 보여줄 수 있습니다. 이렇게 되면 사용자는 페이지 이동 후 더욱 느리게 서비스를 이용하게 됩니다.
이러한 이유로 인해 React Query는 stale과 cache를 따로 분리하였고 UX를 높일 수 있습니다.
gcTime
- staleTime이 데이터를 다시 가져와야 할 때를 알려주고
- gcTime은 데이터를 캐시에 유지할 시간을 결정
- 데이터와 연관되어 있는 활성화된 useQuery가 없고 데이터가 현재 페이지에 표시되지 않으면 쿨 스토리지 상태로 들어감. 쿼리가 캐시에 있으나 사용되진 않고 유효기간이 정해짐. 그 유효기간이 gcTime. gcTime이 지나면 데이터는 캐시에서 사라짐
- 기본적은 gcTime은 5분. 데이터가 페이지에 표시된 후부터 시간이 측정된다(데이터가 페이지에 표시될 때는 gcTime이 체크되지 않는다는 점)
ex1)
데이터: fresh 상태 / stale 시간 있음 / 캐시 존재 / gc 시간도 유효
=> 이 경우 캐시된 데이터를 표시하고, 데이터를 refetch 하지 않음
ex2)
데이터 stale / 캐시 존재 / gc 시간도 유효
=> 캐시된 데이터를 표시하고, refetch함.
즉, stale상태이므로 refetch가 발생하고 서버에서 새 데이터가 올 때까지 캐시된 데이터를 표시
ex3)
데이터 캐시에 없도, gc 시간 지나서 데이터가 삭제
=> 서버에서 새 데이터를 가져올 동안 보여줄 데이터가 없으니 쿼리는 어떤 데이터도 반환하지 않음
우리가 블로그 게시글에 있는 댓글을 가져오는 쿼리키를 (["comments"])라고 했다고 가정하자.
게시글마다 댓글이 바뀌어야 하는데 바뀌지 않는 문제가 생겼다.
그 이유는 모두 동일한 (["comments"])쿼리키를 가지고 있기 때문.
트리거가 있을 때 가져오는 데 얘를 들어. refetch 함수 혹은 컴포넌트가 다시 마운트하거나, 창이 포커싱 되거나, 변형 후 쿼리를 무효화 하는 것이다.
무효화 하는 방법은 블로그 포스트1에 대한 코멘트를 가져올 때 다른 포스트2에 대한 캐시를 제거할 필요는 없다.
심지어 같은 쿼리를 수행하지도 않기 때문에, 이들이 캐시에서 같은 공간을 차지할 필요도 없다.
블로그 포스트1을 클릭하면 1에 대한 캐시 데이터를 보고싶지, 포스트2의 코멘트로 덮어쓰고 싶지 않을 것임
여기서 쿼리 키 배열의 두번 째 요소를 추가해주는 것
["comments", post.id]
이렇게하면 post.id가 업데이트될 때 리액트 쿼리는 새로운 쿼리를 생성시킨다.
각 쿼리는 개별적인 stale시간과 개별적인 캐시 시간을 가진다(gcTime)
prefetching
- 데이터를 캐시에 넣어줌
- 페이지네이션 등을 할 때 등 미리 가져올 수 있어 사용자 경험을 향상시킬 수 있음
useEffect(() => {
if(currentPage < 10 // maxPage){
const nextPage = currentPage + 1;
queryClient.prefetchQuery({
queryKey: ["posts", nextPage],
queryFn: () => fetchPosts(nextPage),
});
}
}, [currentPage, queryClient]);
이런 코드를 작성하여 현재 내가 1페이지면 2페이지의 게시글을 미리 가져와 캐시에 넣어 로딩페이지가 아닌 미리 패치된 데이터를 보여주어 UX를 향상시켜줄 수 있다!!
prefetching의 목적은 보여줄 캐시된 데이터가 있어서 먼저 캐시 데이터를 보여주고
백그라운드에서 조용히 서버를 확인해 데이터가 업데이트됐는지 확인하는 것.
데이터가 업데이트 됐다면, 페이지에 업데이트된 데이터를 제공하려는 것.
이 과정에서 우리는 미리 캐시된 데이터가 있기에 업데이트되어도 사용자에게 보여줄 수 있는 데이터가 있음!!
prefetch된 데이터는 자동으로 stale된 상태 (prefetching의 staleTime은 기본 0초)
예를들어 현재 내가 3페이지를 보고 있음. 3페이지의 비동기 쿼리 함수가 실행되면서 prefetching이 현재 페이지보다 1페이지 많은 4페이지를 쿼리 키로 가지고 있어 데이터를 가져옴. 해당 데이터는 바로 stale상태가 됨. 이제3페이지에서 4페이지로 넘어갈 때 stale 상태이므로 다시 4페이지에 대한 비동기 쿼리 함수가 실행이 됨. 여기서 미리 캐시된 데이터가 있기 때문에 함수가 실행되는 과정에서(데이터가 아직 반환되기 전) 유저에게 데이터를 보여줄 수 있음.
(stale 상태에서만 refetch가 가능 )
Mutation
서버에서 실제 데이터를 업데이트하는 것
변경사항을 등록하는 방법
1. Optimistic updates
- assume change will happen
- 서버 호출이 잘될 거라 가정하고, 잘 안됐을 경우 되돌리는 방법이다.
2. Update React Query cache with data returned from the server
- 서버에서 받은 데이터를 가져오는 것
- mutation 호출을 실행할 때 업데이트 된 데이터를 가져와 리액트 쿼리 캐시를 업데이트 하는 것
3. Trigger re-fetch of relevant data(invalidation)
- 쿼리 무효화
- 클라이언트의 데이터를 서버의 데이터와 동기화하기 위해 서버에 재요청이 발생
useMutaion은 mutate 함수를 반환.
이 mutate 함수는 실제로 서버에 변경 사항을 호출할 때 사용한다,
useMutation은 데이터를 저장하지 않기에 쿼리 키가 필요하지 않음
useMutation에는 isLoading은 있지만 isFetching은 없다. isFetching은 캐시된 데이터가 없어서 의미가 없기 때문
데이터를 가져오는 재시도를 3번하는 useQuery와 달리 재시도도 없음(원한다면 설정을 통해 재시도를 할 수 있긴 함)
useMutation의 반환값의 속성이 useQuery에 비해
훨씬 적다는 것을 알 수있죠
useMutation이 덜 복잡한데요 주로 캐시가 없기 때문이에요
캐시 데이터도 없고, 다시 가져올 데이터도 없고, stale시간도 없어요
그리고 여기엔 isLoading과 isFetching 구분도 없다는 걸 알게 될 거예요
여기엔 isPending만 있어요
mutationFn.refetch()
를 해주면 mutation함수가 리셋된다.
useInfiniteQuery
useQuery를 사용하면, 쿼리 함수에서 데이터가 data 속성에 담겨 반환되지만 useInifiniteQuery에서는 반환되는 객체가 data와 pages라는 두 가지 속성을 포함한다.
- pages는 각 데이터 페이지를 나타내는 객체의 배열
- pageParams: 각 페이지마다 사용하는 파라미터를 기록(자주 사용되지는 않음)
- pageParma: 쿼리 함수에 전달되는 매개변수
'React' 카테고리의 다른 글
[에러 해결]Cannot add property, object is not extensible (0) | 2023.08.28 |
---|---|
state 지연 초기화 (0) | 2023.08.01 |
렌더링 로직과 데이터 받아오는 로직 분리 (0) | 2023.08.01 |
반복문을 객체 혹은 배열로 변경해보기 (0) | 2023.08.01 |
lazy&suspense (0) | 2023.07.17 |