https://www.udemy.com/course/react-query-react/
udemy의 'React Query / TanStack Query : React로 서버 상태 관리하기' 강의를 보고 정리했습니다.
Query Key
post에 해당하는 댓글을 가져오기 위해, 동일한 로직으로 댓글을 불러왔다.
하지만 다른 게시글임에도 불구하고 동일한 코멘트가 렌더링 된다. 다른 포스트를 불러와도 모두 같은 코멘트가 보이게 된다.
이유는 위에서 사용한 queryKey 때문이다.
모든 쿼리 키에 ‘comments’라는 동일한 쿼리 키가 적용되어 있다. 리액트 쿼리에서는 쿼리 키가 동일한 경우 해당 쿼리의 데이터는 이미 캐시 되어 있으므로 새로운 데이터를 가져오지 않는다. 대신에 트리거가 발생했을 때, 새로운 데이터를 가져와서 캐시를 업데이트하고 이를 사용하여 UI를 업데이트합
트리거의 실행조건으로는 컴포넌트를 다시 마운트 하거나, 창을 다시 포커싱 하거나, useQuery에서 반환된 Refetch 함수를 수동으로 실행하거나, 지정된 간격에서 refetch가 일어나거나, mutation 이후 쿼리를 무효화하는 것이다.
새 블로그 포스트 제목을 클릭할 때는 트리거의 예시 중 어느 것도 일어나지 않으며, 따라서 데이터가 stale어도 refetch가 일어나지 않았던 것.
query Key의 의존성 배열
[‘comments’] 와 같은 쿼리 키의 배열에 [‘comment’ , post.id]와 같은 두 번째 요소를 추가할 수 있다.
React Query는 쿼리 키가 변경될 때, 새로운 쿼리를 생성한다. 위의 경우 post.id가 업데이트될 때 리액트 쿼리는 새로운 쿼리를 생성한다. 그래서 각 쿼리는 개별적인 staleTime과 개별적인 캐시시간을 갖는다. 의존성 배열이 다르면, 완전히 다른 것으로 간주되는 것.
React Query에서 데이터를 캐시하고 관리할 때, 쿼리 키는 데이터를 고유하게 식별할 수 있는 값을 포함해야 한다.
정리) 같은 queryKey를 사용하고 있었기에, post.id 따라 다른 요청을 보내도 리액트 쿼리는 캐시에 데이터가 있다고 판단해서, 새로 가져오지 않았던 것.
고유하게 식별할 수 있는 값을 붙여주면, 식별 값에 따라 각각의 캐시를 갖는 것을 확인할 수 있다.
pagination
리액트 쿼리를 통해 페이지네이션을 관리해 볼 것이다.
위의 comment 사례처럼 각 페이지마다 다른 query key를 사용해야 한다. 따라서 쿼리키에 의존성 배열을 추가해서, 가져오려는 해당 페이지 번호를 포함시킬 것이다.
사용자가 버튼을 클릭해서 다음이나, 이전 페이지로 가면, 현재 페이지 상태가 업데이트되며, 리액트-쿼리가 query key의 변화를 감지하고 새 쿼리를 실행할 것이다.
currentPage State를 통해 페이지 번호를 관리하고, queryKey에 포함시킨다. 이제 페이지가 바뀔 때마다, [‘post’, currentPage] 쿼리 키를 추적할 것이다. 또한 페이지 번호에 해당하는 정보를 요청하기 위해 queryFn에도 인자로 페이지 번호를 넘겨준다
이제 버튼을 눌렀을 때, 페이지 번호를 증가/감소시키면 된다.
버튼을 누를 때마다, 페이지 번호가 증가/감소되며 쿼리 키가 변경된다. 이에 따라 새로운 데이터를 호출하게 된다.
하지만 페이지를 넘겼을 때 해당 데이터를 불러오는 동안 Loding 멘트가 잠깐 렌더링 되는 등 사용자 경험이 좋지 않다. 캐시가 없기 때문에 useQuery가 반환하는 isLoading이 true가 되기 때문이다. 이를 해결하기 위해 데이터 Pre-fetching에 대해 알아볼 것이다.
Data Prefetching
데이터를 미리 가져오고(prefetching) 캐시에 넣어 사용자가 기다리지 않게 할 것이다.
prefetching은 페이지네이션뿐 아니라, 사용자가 원하는 모든 데이터에 사용할 수 있다. 사용자가 웹 사이트 방문 시 통계적으로 다음에 방문할 특정 탭이 있을 때, 해당 페이지의 데이터를 미리미리 prefetching 하는 것이다.
페이지네이션 기준으로, 현재 페이지에서 다음 페이지로 넘어갈 때, 다음 페이지에 대한 캐시가 있어야 Loading이 렌더링 되지 않을 것이다.
pre-fetching을 위해서 useQueryClient 훅을 사용한다. 코드를 설명하면, 현재 페이지가 최대 페이지보다 작을 때만 prefetch를 진행하며, useEffect를 통해서, currentPage(현재 페이지)가 변경될 때마다 실행한다.
queryKey는 useQuery와 같으나, 다음 페이지의 정보를 미리 받아와야 하기에, 의존성 배열로 다음 페이지의 값을 넣어준다.
currentPage가 1로, 첫 페이지지만 다음 페이지의 정보(2)가 prefetch 된 확인할 수 있다.
위에서 queryClient를 의존성 배열에 추가하는 이유를 찾다가 동일한 질문을 발견해서 해당 답변을 가져왔다
1. useEffect 내부에서 참조하는 외부 변수나 함수가 있다면, 해당 변수나 함수를 의존성 배열에 명시해야 함.
2. useQueryClient에서 반환되기에, queryClient는 일정한 값이 보장되지 않음. 그래서 react는 값이 언제든지 변할 수 있다고 이에 따라 의존성 배열에 포함시켜야 한다.
즉 정리하면.. 실제로 우리가 queryClient를 변경되지 않게 사용할지라도, 리액트는 변경 가능성이 있다고 판단하기에, 의존성 배열에 추가함으로써 리액트에게 변경될 수 있다는 것을 명시적으로 알려주는 것.
isFetching vs isLoading
isFetching은 비동기 쿼리 함수가 아직 해결되지 않았을 때 true이다. 데이터를 아직 가져오고 있는 상태이다. isLoading은 isFetching인 상태이고, 해당 쿼리에 대해 캐시 된 데이터가 없는 상태이다.
isFetching은 캐시 된 데이터가 있는지 없는지 관계없이 queryFn이 실행 중이면 true를 반환한다. 그래서 isFetching으로 Loading 페이지를 띄운다면, 캐시가 있던 없던 무조건 Loading 페이지가 나올 것이다.
이전에 적용한 prefetching의 목적은 데이터가 업데이트 됐다면 페이지에 해당 데이터를 제공하려는 것이었다.
예시)
현재 1페이지에서, prefetching을 사용해서 2페이지의 값을 미리 받아왔다고 하자. 그럼 2페이지의 캐시는 존재하나, stale상태일 것이다. 사용자가 2페이지로 이동했을 때, 리액트 쿼리는 stale 상태를 확인할 것이고 바로 queryFn이 실행된다. 그리고 데이터를 가져오는 동안은 기존의 캐시 된 정보를 보여줄 것이다.
여기서 만약 isFetching을 통해 Loading 화면을 띄운다면 prefetching으로 미리 받아온 캐시의 여부와 상관없이 Loading 페이지가 렌더링 될 것이다. 즉 prefetching을 한 이유가 없어지는 것이다. 그래서 이런 상황에서는 isLoading이 더 적절하다
Mutation
mutation이란 서버에 네트워크를 호출해 서버에서 실제 데이터를 업데이트하는 것을 말한다. 블로그에서 포스트를 추가, 삭제 및 제목 변경등을 하는 경우이다.
useMutations을 사용할 것이다, useMutation은 useQuery와 매우 유사하지만 몇 가지 차이점이 있다.
1. useMutation은 mutate 함수를 반환한다.
2. 데이터를 저장하는 것이 아니기에, query Key가 필요 없다.
3. useQuery가 3번 재시도하는 것에 반해, useMutation은 재시도를 하지 않는다. 다만 설정을 통해 자동 재시도를 할 수 있음
useMutation에는 queryKey가 필요하지 않다. 캐시 데이터와 관련이 없기 때문이다. useMutation은 mutate 함수를 반환하는 객체이다. 그래서 누군가 삭제 버튼을 클릭할 때, mutate 함수를 실행시키면 된다.
useMutation의 반환 값인 deleteMutation을 props로 내려주고, 삭제 버튼에 onClick으로 deleteMutation의 mutate 속성을 실행시킨다. 이때 삭제시킬 post.id를 인수로 넘겨주면 deletePost로 전달되어서 게시물이 삭제될 것이다.
useMutation의 반환값의 속성이 useQuery에 비해, 훨씬 적다. 주로 캐시가 없기 때문이다. 캐시 데이터도 없고, 다시 가져올 데이터도 없고, stalteTime도 없다. 또한 isLoading과 isFetching의 구분 없이 isPending만 있다.
조건부 렌더링을 통해, delete요청이 실행될 때, mutation 속성의 값에 따라 다른 값들이 출력되게 했다.
요청이 진행 중일 때 isPending이 true가 되며, 해당 멘트가 렌더링 되었고, 요청이 성공했을 때 isSuccess가 true가 되며 해당 멘트가 렌더링 되었다.
하지만 이대로면 mutation의 상태가 유지된다. 한 번 삭제 요청을 보낸 이후에, 다른 게시물을 클릭하면, 요청을 보내지 않았음에도, 조건부 렌더링에 의해 메시지가 렌더링 된다.
따라서 게시글이 클릭되었을 때 deleteMutation.reset()을 통해 모든 속성을 초기화한다.
다른 게시글을 클릭했을 때 mutation 속성이 초기화되어, 메시지가 출력되지 않음을 확인할 수 있다.
'프로그래밍 언어 > TanStack-Query' 카테고리의 다른 글
쿼리 생성 및 로딩/에러 상태 (0) | 2024.02.22 |
---|