react-query 잘 쓰고 싶으면 알아야 할 개념 모음

Q&A 큐레이션

1. react-query useQuery vs useMutation (데이터 조회 시)

안녕하세요. react-query에서 useQuery와 useMutation을 쓰는 도중 의문이 생겨서 질문드립니다. 보통 Read 작업은 useQuery를 쓰고, Create/Update/Delete 작업은 useMutation을 쓰라고들 말씀하시는데요, 특정 페이지 렌더링 시 자동 fetch하지 않고, 사용자 액션 발생 시에만 fetch 요청을 하는 경우 useQuery가 반환하는 refetch 함수를 쓰거나 useMutation가 반환하는 mutate 함수를 사용하면 구현이 가능하더라구요. (제가 테스트한 코드는 gist에 있습니다.) 여기서 궁금한 건 Read 작업임에도 불구하고 useMutation으로도 구현이 가능하다면 굳이 위 상황에서 useQuery로 구현했을 때의 이점이 있을까요?


답변

안녕하세요! 위 케이스와 유사한 질문에 답한 기억이 있는데, 참고해보시면 좋을 것 같아요 - https://careerly.co.kr/qnas/1165 우선 useQuery와 useMutation의 일반적인 차이는 알고 계신 것 같아서 생략하겠습니다. 근본적으로 둘은 외부 요청을 래핑한 개념이라는 공통점이 있습니다. 하지만 메인테이너의 말을 빌리자면 둘의 차이는 선언적 vs 명령적 차이라고 합니다. (참고: https://tkdodo.eu/blog/mastering-mutations-in-react-query#differences-to-usequery) 즉, useQuery (선언적)를 쓴다면 react-query에서 제공하는 여러 기능들 (ex. refetchOnWindowFocus, enabled)이 "알아서" 돌아가지만, useMutation (명령적)의 경우는 이런 기능들이 "알아서" 돌아가지 않습니다. 이유는 useQuery의 경우 선언하는 시점에 observer가 생성되지만 useMutation은 mutate을 사용하기 전까지 observer가 생성되지 않습니다. 또한 useMutation의 observer는 mutate가 돌아갈때마다 이전 observer를 unsubscribe 합니다. 다시 질문에 답하자면, 질문자님의 코드에서 데이터를 요청하는 시점은 사용자가 버튼을 클릭했을 때입니다. 그러면 사실상 mutate이나 refetch나 API 요청을 한번 래핑한 함수라서 동일하게 작동할겁니다. 다만, useQuery의 경우 결과가 캐싱이되어서 다른 곳에서 사용해야 할 경우 동일한 키를 제공해주면 공유가 가능합니다. (결과뿐만 아니라 로딩 상태, 에러 상태 등도 같이 공유됩니다) 하지만 refetch/mutate로 가져온 데이터를 굳이 캐싱해서 사용할 이유가 없고 위에 성원님이 말씀하신 컨벤션 정도를 제외한다면, 질문자님의 질문처럼 useQuery를 사용하는 이점이 퇴색될 것 같습니다. 오히려 useQuery를 사용해서 "알아서" 돌아가는 기능들 때문에 의도치 않은 이슈가 발생할수도 있어보이네요. ex. 의도치 않은 refetchOnWindowFocus 같은게 돌아서 onSuccess가 또 호출되는 경우? 정도가 생각나네요. 이것에 대해서 찾아보다보니, 이미 깃허브 이슈에서 비슷한 논의를 한적이 있어서 이것도 링크 남길게요. 질문자와 TkDodo님의 답변을 참고하시면 좋을 것 같습니다 :) - https://github.com/TanStack/query/issues/2304#issuecomment-1011598184

외 2개 답변 보러 가기

2. next.js swr vs react-query 어떤 라이브러리를 선택해야 할까요?

안녕하세요, next.js로 처음 프로젝트 시작하는 초보 개발자입니다! 지금까지 React로만 개발해오고 상태관리 라이브러리는 Redux만 사용해봐서 이번에 새로운 라이브러리를 사용해보려고 합니다. next.js에서는 swr이나 react-query를 자주 사용한다고 하는데 어떤걸 쓰면 좋을까요? 장단점이나 여러가지 알려주시면 감사하겠습니다🙌


답변

저는 보통 라이브러리 선택시에 다음 조건을 고려합니다. 1. 사용하는 사람이 많고 유지보수가 잘 되고 있는지 2. 작업자에게 익숙한지 3. 구현할 프로젝트에 필요한 기능이 있는지, 성능, 용량 1. 사용하는 사람이 많고 유지보수가 잘 되고 있는지 react-query와 swr npm 트렌드는 50만 다운로드 정도 차이나네요! https://npmtrends.com/react-query-vs-swr swr은 4월 11에 최신 업데이트됐고 react-query는 2일 전입니다. 우선 두 라이브러리다 업데이트가 잘 되고, 많이 사용되고 있는 것 같습니다. 2. 작업자에게 익숙한지 질문자 분께서 두 라이브러리 모두 처음 사용해본다고 하셨으니 같은 조건인 것 같습니다. 3. 구현할 프로젝트에 필요한 기능이 있는지, 용량, 성능 용량: 번들사이즈는 swr이 더 가볍다고 합니다. 성능: swr을 깊게 사용해본적이 없어서 기능 비교는 어렵지만 https://blog.logrocket.com/swr-vs-tanstack-query-react/ 이 블로그를 참고해봤을 때 swr: 가벼운 사이즈로 React 요청 처리에서 캐싱 문제를 해결하기 위한 최소한의 API를 제공. react-query: swr과 동일한 문제를 해결하지만 더 고도화된 기능 제공. 개인적으로는 둘다 사용해보시고 프로젝트에 필요한 기능들을 비교한 뒤 선택하는 것 추천드려요!

외 3개 답변 보러 가기

3. react-query의 useQueries 이용

const results = useQueries([ { queryKey: ['post', 1], queryFn: fetchPost }, { queryKey: ['post', 2], queryFn: fetchPost }, ]) 이런식으로 useQueries를 이용해서 해당 키에 데이터를 넣는데요. 이게 엄청나게 많아지면 시간이 오래걸리지 않을까요? 첫번째 fetch하고 두번째 fetch하고 ..... 비효율적인것 같은데 useQueries를 많이 사용하나요?


답변

useQueries는 하나하나 순서대로 실행하지는 않고 병렬적으로 요청합니다. 물론 너무 많으면 문제가 될 수도 있겠지만 질문자님께서 주신 설명처럼 하나하나 요청을 기다리지는 않습니다. 이건 useQueries뿐만 아니라 useQuery를 여러개 사용했을 때도 동일합니다. 아래 공식문서 참고해보세요 https://tanstack.com/query/v4/docs/guides/parallel-queries#dynamic-parallel-queries-with-usequeries

외 1개 답변 보러 가기

4. react query에서 placeholderData와 initial data 차이

안녕하십니까~ 프로젝트 코드를 보는 중에 react-query의 useQuery 옵션 중에 "placeholderData"라는 값에 더미 데이터를 넣어주고 있습니다. 근데 이걸 initialData라는 옵션에 넣어도 동일하게 작동하길래 둘의 차이가 뭔지 궁금하더라구요 ㅎㅎ 혹시 아시는 분 계신가요?


답변

둘은 비슷해보이지만 용도와 내부 동작이 다릅니다. placeholderData는 실제 데이터에 대한 응답을 받기 전 까지 "임시" 데이터를 보여주기 위한 용도로 사용됩니다. 내가 요청한 API의 응답이 도착하는 순간 화면에 보여지는 데이터는 실제 데이터가 됩니다. initialData는 "초기" 데이터를 보여주기 위한 용도로 사용됩니다. 내가 요청한 API의 응답이 도착하더라도 아직 데이터가 'fresh'한 상태라면 캐시에 남아있는 initialData가 화면에 보여지게 됩니다. 내부적으로 placeholderData는 캐시에 데이터를 저장하지 않는 반면, initialData는 데이터를 캐시에 저장합니다. 질문자 분이 말씀하신 것처럼 실제로 테스트 해보면 둘의 동작이 같다고 느껴지는데, 기본적으로 staleTime이 `0`으로 설정되있기 때문입니다. initialData가 존재하고 staleTime이 `0`이면 React Query는 곧바로 refetch를 실행해서 새 데이터를 가져옵니다. staleTime을 `1000*60`(ms)으로 바꿔보면 화면에 initialData가 1분 동안은 계속 남아있는 것을 확인할 수 있습니다. 헷갈리시면 placeholderData의 staleTime을 첫번째는 `0`으로, 두번째는 `1000*60`으로 한 번 바꿔보세요. 같은 결과를 확인하실 수 있을겁니다. placeholderData는 캐시에 아무것도 남지 않기 때문에 staleTime에 영향을 받지 않습니다. 둘의 차이점에 대한 자세한 내용은 React Query 메인테이너분이 작성하신 https://tkdodo.eu/blog/placeholder-and-initial-data-in-react-query 글을 읽어보시는 걸 추천드립니다.

이 질문 바로 가기

5. react-query에서 랜더링 할 때마다 api를 계속 요청합니다.

react-query를 처음 사용해 보는데요. query-key에 대해서 데이터를 캐싱해놓고 꺼내서 사용할 수 있다고 해서 사용해보았습니다. 데이터가 이미 캐싱되어 있고 데이터에 변화가 없는 상태인데도 계속해서 데이터를 새로 불러오도록 API를 요청합니다. cacheTime이라는 것이 있는데 이것도 기본 5분으로 설정되어 있다는데 왜 안되는 걸까요?


답변

cacheTime말고 staleTime이라는 것이 있습니다. staleTime은 해당 데이터가 얼마나 신선(?) 한지를 나타내는 기간입니다. default값은 0으로 되어있어서 매번 data가 stale하다고 판단되어 API를 요청하는 것입니다. staleTime을 주면 해당 기간동안 API를 요청하지 않을거에요! cacheTime, staleTime 관련 글: https://2ham-s.tistory.com/407 공식문서 내용입니다. staleTime: number | Infinity - Optional - Defaults to 0 - The time in milliseconds after data is considered stale. This value only applies to the hook it is defined on. - If set to Infinity, the data will never be considered stale

이 질문 바로 가기

6. React Query 데이터 재조회 방식

안녕하세요. 이번 프로젝트에서 React Query를 처음 써보게 되었는데요. 데이터를 조회해올 때, 페이지와 검색어가 바뀔 때마다 API Call을 하는 것이 목적입니다. 제가 아는 방식은 아래와 같이 2가지인데, 두 방식이 어떤 차이가 있고, 어떤 선택이 좋은 선택인지 분간이 잘 되지 않아서 질문드립니다. (폰으로 작성중이라 코드가 좀 가물가물해서 문법이 약간 틀렸을 수도 있습니다!) ---------------------------------------- 1. useQuery만 사용 useQuery(['someList', page, keyword], () => { const params = { page, keyword, }; fetchList(params); }, { onSuccess: () => { // do something } }); 2. useQuery, useMutation, useEffect 사용 useQuery('someList', () => { const params = { page, keyword, }; fetchList(params); }, { onSuccess: () => { // do something } }); const { mutate } = useMutation(fetchList, { onSuccess: () => { queryClient.invalidateQueries('someList'); } }); useEffect(() => { const params = { page, keyword, }; mutate(params); }, [page, keyword]); ---------------------------------------- 사실 제가 아는 useMutation의 용도(생성, 수정, 삭제)와 코드 사이즈 측면을 생각해서 실제론 1번 방식을 사용하고 있기는 한데, 동작상 두 방식의 정확한 차이를 모르겠어서 이렇게 질문드립니다. 혹시 두 방식 모두 일반적이지 않다면, 일반적인 방식도 함께 알려주시면 정말 감사드리겠습니다!


답변

안녕하세요! 위에 올리신 코드를 기반으로 gist를 만들어보았는데요. https://gist.github.com/json9512/8bc447d0c7206ca73daca5da79531e59 1번 방법이 좀 더 일반적인 방법인 것 같습니다. 아시다시피 useMutation은 CUD을 위한 훅이고 useMutation으로 GET 요청을 하는 것은 설계 용도를 벗어나는 사용법이 아닌가 싶습니다. 우선 1번과 2번의 차이는 1번의 경우 param이 바뀔때 서버로 요청을 1번만 보내게 됩니다. 2번의 경우는 2번 보냅니다. mutate를 할때 fetchList로 한번 보내고 이후 "someList" 키가 invalidate 되면서 useQuery가 한번 더 fetchList를 호출합니다. 이론적으로 useQuery는 여러 컴포넌트에서 호출해도 같은 값을 공유하지만, useMutation의 경우는 값을 공유하지 않는 것으로 알고 있습니다. 다만, mutation의 상태를 저장/관리하기 위해 MutationCache라는 곳에 mutation 자체와 관련 데이터를 따로 저장하는데요. 이걸 감안한다면 2번 방법은 메모리도 더 많이 쓰는 방법이 되는 것 같네요. 정리를 하자면: - 1번 방법이 일반적인 방법인 것 같습니다. query key에 page, keywords를 넣어서 관리해도 무방해요. - 2번 방법이 메모리 차원에서 더 비효율적입니다 - 2번 방법은 네트워크 요청을 2번 보냅니다 - 2번 방법은 훅이 설계된 의도대로 사용되지 않아서, 버그를 발생 시킬 수 있습니다 - (사견) 2번 방법은 코드 의도를 파악하기 쉽지 않은 것 같습니다 - (사견) page, keyword 조합마다 쿼리가 저장될텐데, 캐싱이 꼭 필요한건지/유용하게 사용할 수 있는지도 고민이 필요한거 같습니다 여담으로 useEffect는 사용할 때 꼭 필요한것인지 고민하면 좋습니다. 참고할만한 링크들 첨부합니다: - https://beta.reactjs.org/learn/you-might-not-need-an-effect - https://tkdodo.eu/blog/mastering-mutations-in-react-query - https://tanstack.com/query/v4/docs/reference/MutationCache - https://tanstack.com/query/v4/docs/reference/useMutation - https://tanstack.com/query/v4/docs/reference/useQuery

외 1개 답변 보러 가기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!

현직자들의 명쾌한 답변을 얻을 수 있어요.

또는

이미 회원이신가요?

키워드로 질문 모아보기

실무, 커리어 고민이 있다면

새로운 질문 올리기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!