[React-Query] React-Query 개념잡기

오형근·2023년 4월 24일
185

Frontend

목록 보기
6/10
post-thumbnail

기존 글은 @tanstack/react-query v4를 기반으로 작성되었으나, v5의 내용을 반영하여 수정하였습니다.
tanstack scope 하위에서 react-query라는 이름을 사용하고 있기 때문에 react-query라는 명칭을 사용하여 작성하였습니다.
공식문서

⏳ React-Query?

React-Query, 프론트엔드 공부를 하면서 분명 몇 번 들어본 적도 있고, velog 눈팅하다가 힐끗 본 적도 있는 라이브러리인데…도대체 뭐하는 친구지?? 이번 글을 통해 정확하게 알아보자.


👇 공식문서에서는 React-Query를 다음과 같이 설명하고 있다.

💡 fetching, caching, 서버 데이터와의 동기화를 지원해주는 라이브러리

설명을 간단히 해보면 이름처럼 React 환경에서의 비동기 Query(질의) 과정을 도와주는 라이브러리이다!! if(kakao)2021 컨퍼런스에서 언급된 내용 중, 아래와 같은 내용이 있었다.

🙌 「if(kakao)2021 - 카카오페이 프론트엔드 개발자들이 React Query를 선택한 이유」 세줄요약 🤟


1. React Query는 React Application에서 서버 상태를 불러오고, 캐싱하며, 지속적으로 동기화하고 업데이트하는 작업을 도와주는 라이브러리입니다.
2. 복잡하고 장황한 코드가 필요한 다른 데이터 불러오기 방식과 달리 React Component 내부에서 간단하고 직관적으로 API를 사용할 수 있습니다.
3. 더 나아가 React Query에서 제공하는 캐싱, Window Focus Refetching 등 다양한 기능을 활용하여 API 요청과 관련된 번잡한 작업 없이 “핵심 로직”에 집중할 수 있습니다.

즉, React-Query는 프론트엔드에서 비동기 데이터를 불러오는 과정 중 발생하는 문제들을 해결해준다는 건데, 어떤 문제들을 어떻게 해결한다는 걸까?


1. 캐싱(Caching)

React-Query의 장점 중 하나는 데이터를 캐싱한다는 점이다.

캐싱이란 특정 데이터의 복사본을 저장하여 이후 동일한 데이터의 재접근 속도를 높이는 것을 말한다!

React-Query는 캐싱을 통해 동일한 데이터에 대한 반복적인 비동기 데이터 호출을 방지하고, 이는 불필요한 API 콜을 줄여 서버에 대한 부하를 줄이는 좋은 결과를 가져온다.

💡 최신의 데이터인지 어떻게 판별하는데??

❔여기서 궁금한 것은 데이터가 최신의 것인지 아닌지에 대한 것이다.

만일 서버 데이터를 불러와 캐싱한 후, 실제 서버 데이터를 확인했을 때 서버 상에서 데이터의 상태가 변경되어있다면, 사용자는 실제 데이터가 아닌 변경 전의 데이터를 바라볼 수밖에 없게 된다. 이는 사용자에게 잘못된 정보를 보여주는 에러를 낳는다.

💡 참고로, React-Query에서는 최신의 데이터를 fresh한 데이터, 기존의 데이터를 stale한 데이터라고 말한다!!


언제 데이터를 갱신해야하지?

위와 같은 에러를 발생시키지 않는 좋은 캐싱 기능을 제공한다는 것은 결국 필요한 상황에 적절하게 데이터를 갱신해줄 수 있다는 말과 같다. 그럼 그런 상황은 언제일까?

  1. 화면을 보고 있을 때
  2. 페이지의 전환이 일어났을 때
  3. 페이지 전환 없이 이벤트가 발생해 데이터를 요청할 때

크게 보면 위의 3가지로 나눌 수 있다. 이를 위해 React-Query에서는 기본적인 아래의 옵션들을 제공한다.

refetchOnWindowFocus, //default: true
refetchOnMount, //default: true
refetchOnReconnect, //default: true
staleTime, //default: 0
cacheTime, //default: 5분 (60 * 5 * 1000)

❗위의 옵션들을 통해 우리는 React-Query가 어떤 시점에 데이터를 Refetching하는지 알 수 있다.

  1. 브라우저에 포커스가 들어온 경우(refetchOnWindowFocus)
  2. 새로운 컴포넌트 마운트가 발생한 경우(refetchOnMount)
  3. 네트워크 재연결이 발생한 경우(refetchOnReconnect)

staleTime? cacheTime ?

staleTime

  • staleTime은 데이터가 fresh → stale 상태로 변경되는 데 걸리는 시간이다.
  • fresh 상태일 때는 Refetch 트리거(위의 3가지 경우)가 발생해도 Refetch가 일어나지 않는다!
  • 기본값이 0이므로 따로 설정해주지 않는다면 Refetch 트리거가 발생했을 때 무조건 Refetch가 발생한다!

cacheTime

  • cacheTime은 데이터가 inactive한 상태일 때 캐싱된 상태로 남아있는 시간이다.
  • 특정 컴포넌트가 unmount(페이지 전환 등으로 화면에서 사라질 때) 되면 사용된 데이터는 inactive상태로 바뀌고, 이때 데이터는 cacheTime만큼 유지된다.
  • cacheTime 이후 데이터는 가비지 콜렉터로 수집되어 메모리에서 해제된다.
  • 만일 cacheTime이 지나지 않았는데 해당 데이터를 사용하는 컴포넌트가 다시 mount되면, 새로운 데이터를 fetch해오는 동안 캐싱된 데이터를 보여준다.
  • 즉, 캐싱된 데이터를 계속 보여주는게 아니라 fetch하는 동안 임시로 보여준다는 것이다!!

이외에도 사용자가 특정 이벤트가 발생했을 때 Refetching을 하도록 설정해줄 수 있다. React-Query의 이러한 기능들을 통해 사용자는 언제나 최선의 데이터를 제공받게 된다.

2. Client 데이터와 Server 데이터 간의 분리

프로젝트의 규모가 커지고 관리해야할 데이터가 넘치다 보면, Client 에서 관리하는 데이터와 Server 에서 관리하는 데이터가 분리될 필요성을 느낀다.

Client Data: 모달 관련 데이터, 페이지 관련 데이터 등등..
Server Data: 사용자 정보, 비즈니스 로직 관련 정보 등등..
간단하게 생각해서 비동기 API 호출을 통해 불러오는 데이터들을 Server 데이터라고 할 수 있다.

실제 Client 데이터의 경우 Redux, Recoil, mobX와 같은 전역 상태 관리 라이브러리들을 통해 잘 관리되어오고 있으나, 문제는 이러한 라이브러리들이 Server 데이터까지도 관리를 해야하는 상황이 발생한다는 것이다.

위의 상태 관리 라이브러리에도 비동기 함수를 처리하는 로직이 존재하거나, 서드 파티를 라이브러리를 지원하는 것이 많다. 그러나 이들이 Client 데이터와 Server 데이터를 완벽히 분리하여 관리에 용이하도록 충분한 기능이 지원된다고 보기 어렵다. 즉 위의 라이브러리들은 Client 데이터를 관리하는데 로직이 집중되어있기 때문에, Server 데이터까지 효율적으로 관리하기에는 한계가 분명하다.

👇 React-Query는 이러한 문제에 대한 해결책 또한 제시해 주는데, 아래 코드를 살펴보자.

// 해당 내용은 v4의 문법을 기반으로 한 내용입니다. 
// 단순히 개념적인 예시를 설명하기 위함입니다. 
// v5 예시는 아래에서 확인하실 수 있습니다.
const { data, isLoading } = useQueries({
	['unique-key'],
	() => {
		return api({
			url: URL,
			method: 'GET',
		});
	},
	{
		onSuccess: (data) => {
			// data로 이것저것 하는 로직
		}
	},
	{
		onError: (error) => {
			// error로 이것저것 하는 로직
		}
	}
})

예시에서는 컴포넌트 내부에서 위와 같은 로직을 통해 Server 데이터를 가져오고 있는데, 이때 onSuccess와 onError 함수를 통해 fetch 성공과 실패에 대한 분기를 아주 간단하게 구현할 수 있다. 이는 Server 데이터를 불러오는 과정에서 구현해야할 추가적인 설정들을 진행할 필요가 없다는 이야기이다.

👉 즉, Client 데이터는 상태 관리 라이브러리가 관리하고, Server 데이터는 React-Query가 관리하는 구조라고 생각하면 된다!! 이를 통해 우리는 Client 데이터와 Server 데이터를 온전하게 분리할 수 있다.

🔎  물론 여기서 React-Query가 가져온 Server 데이터를 상태 관리 라이브러리를 통해 전역 상태로 가져올 수도 있는 건 사실이다. 그러나 refetch가 여러 번 일어나는 상황에 매번 Server 데이터를 전역 상태로 가져오는 것이 옳은지 판단하는 것은 여러분의 몫이다. 개발하는 서비스의 상황에 맞게 잘 선택해보도록 하자!!

3. React-Query가 데이터를 다루는 방법

React-query를 사용하면서 에러를 겪으신 분들이나 프론트엔드 라이브러리 코드를 조금이라도 뜯어보신 분들은 쉽게 알 수 있을텐데, React-query 또한 React의 ContextAPI를 기반으로 동작한다.

전체 Scope가 되며 하위 컴포넌트들의 server data를 관리하는 QueryClient가 존재하는데, 해당 QueryClient는 우리가 Query를 사용할 때 명시하는 Key를 기반으로 데이터를 저장한다.

즉, 다시 말해 QueryClient는 단순히 서버에서 불러온 데이터를 저장하는 곳이라기보다는, 데이터를 저장하는 용도로 사용되는 Context Store와 동일한 역할을 한다는 것이다. 그 용도가 서버 데이터 관리에 초점을 두는 것일 뿐이기에, 우리는 필요에 따라 QueryClient 또한 단순 데이터 저장소처럼 간주하고 자유자재로 활용할 수 있어야한다.

실제 tanstack/@react-query공식문서를 살펴보면 getQueryData, setQueryData, fetchQuery 등등 QueryClient 라는 저장소를 관리할 수 있는 정말 다양한 메서드가 존재한다. 해당 메서드들은 데이터를 어떠한 방식(비동기/동기, 캐싱 등)으로 관리할지에 따라 나뉘며, 각 메서드의 특징과 활용 용도를 정확하게 파악하고 있어야 이 라이브러리를 제대로 활용하고 있다고 말할 수 있다.

React-query는 타 툴들에 비교하면 덩치가 나름 있는 라이브러리이기 때문에 추후 번들 사이즈에서 단점이 드러날 수 있는 여지가 있다. 따라서 사용하고자 한다면 위와 같은 다양한 도구들을 제대로 활용하여 번들의 메모리를 헛되이하지 않는 현명한 프론트엔드 개발자가 되자.

4. 대표적인 기능들

React-Query에서 data fetching을 위해 제공하는 대표적인 기능들을 살펴보자!

기본적으로 GET 에는 useQuery가, PUT, UPDATE, DELETE에는 useMutation이 사용된다.

useQuery

  • 첫 번째 파라미터로 unique key를 포함한 배열이 들어간다. 이후 동일한 쿼리를 불러올 때 유용하게 사용된다.
  • 첫 번째 파라미터에 들어가는 배열의 첫 요소는 unique key로 사용되고, 두 번째 요소부터는 query 함수 내부의 파라미터로 값들이 전달된다.
  • 두 번째 파라미터로 실제 호출하고자 하는 비동기 함수가 들어간다. 이때 함수는 Promise를 반환하는 형태여야 한다.
  • 최종 반환 값은 API의 성공, 실패 여부, 반환값을 포함한 객체이다.

Example

import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from '@tanstack/react-query'

const queryClient = new QueryClient()

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

function Example() {
  const { isPending, error, data } = useQuery({
    queryKey: ['repoData'],
    queryFn: () =>
      fetch('https://api.github.com/repos/tannerlinsley/react-query').then(
        (res) => res.json(),
      ),
  })

  if (isPending) return 'Loading...'

  if (error) return 'An error has occurred: ' + error.message

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
      <strong>👀 {data.subscribers_count}</strong>{' '}
      <strong>{data.stargazers_count}</strong>{' '}
      <strong>🍴 {data.forks_count}</strong>
    </div>
  )
}
  • useQuery 함수가 반환하는 객체를 보면 isPending 을 통해 로딩 여부를, error 를 통해 에러 발생 여부를, data를 통해 성공 시 데이터를 반환할 수 있다.

  • isPendingerror를 이용하여 각 상황 별 분기를 쉽게 진행할 수 있다.

useQuery 동기적으로 실행

  • useQuery에서 enabled 옵션을 사용하면 비동기 함수인 useQuery를 동기적으로 사용 가능하다.
  • useQuery의 세 번째 인자로 다양한 옵션 값들이 들어가는데, 여기서 enabled에 값을 대입하면 해당 값이 true일 때 useQuery를 동기적으로 실행한다!
const { data: todoList, error, isFetching } = useQuery({
	queryKey: ["todos"], 
  	queryFn: fetchTodoList,
});
const { data: nextTodo, error, isFetching } = useQuery({
  queryKey: ["nextTodos"],
  queryFn: fetchNextTodoList,
  enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다
});

useQueries

여러 개의 useQuery를 한 번에 실행하고자 하는 경우, 기존의 Promise.all()처럼 묶어서 실행할 수 있도록 도와준다!

const ids = [1, 2, 3]
const results = useQueries({
  queries: ids.map((id) => ({
    queryKey: ['post', id],
    queryFn: () => fetchPost(id),
    staleTime: Infinity,
  })),
})

// 두 query에 대한 반환값이 배열로 묶여 반환된다!!

// 만일 반환된 배열에 대해 통합된 값을 불러오고 싶다면, 아래와 같이 combine 설정을 통해 데이터를 한 번에 반환할 수 있다. 이외에도 배열을 다루는 메서드들을 이용해 반환값에 대한 전처리를 수행할 수 있다!

const ids = [1, 2, 3]
const combinedQueries = useQueries({
  queries: ids.map((id) => ({
    queryKey: ['post', id],
    queryFn: () => fetchPost(id),
  })),
  combine: (results) => {
    return {
      data: results.map((result) => result.data),
      pending: results.some((result) => result.isPending),
    }
  },
})

useMutation

  • 위에서 언급한 것처럼 PUT, UPDATE, DELETE 와 같이 값을 변경할 때 사용하는 API다. 반환값은 useQuery와 동일하다.

빠르게 예시를 살펴보자.

function App() {
  const mutation = useMutation({
    mutationFn: (newTodo) => {
      return axios.post('/todos', newTodo)
    },
  })

  return (
    <div>
      {mutation.isLoading ? (
        'Adding todo...'
      ) : (
        <>
          {mutation.isError ? (
            <div>An error occurred: {mutation.error.message}</div>
          ) : null}

          {mutation.isSuccess ? <div>Todo added!</div> : null}

          <button
            onClick={() => {
              mutation.mutate({ id: new Date(), title: 'Do Laundry' })
            }}
          >
            Create Todo
          </button>
        </>
      )}
    </div>
  )
}
  • 위의 코드에서 볼 수 있듯이 반환값은 useQuery와 동일하지만, 처음 사용 시에 post 비동기 함수를 넣어주었다. 이때 useMutation의 첫 번째 파라미터에 비동기 함수가 들어가고, 두 번째 인자로 상황 별 분기 설정이 들어간다는 점이 차이이다.

  • 실제 사용 시에는 mutation.mutate 메서드를 사용하고, 첫 번째 인자로 API 호출 시에 전달해주어야하는 데이터를 넣어주면 된다!!


↔️ SWR과의 비교

React-Query 가 장점만 보유하고 있다면, 다른 경쟁 라이브러리들이 살아남기 어려울 것이다. 그렇다면 React-Query가 가지는 단점으로는 어떤 것들이 있을까? 이를 React-Query만큼 대표적인 Data fetching 라이브러리인 SWR과 비교하면서 알아보자!

react-query vs SWR

위 사진은 State of js에서 조사한 2022 라이브러리 사용 빈도이다. 보면 React-Query가 더 많이 사용되었지만, SWR도 상당 비율 차지하고 있음을 알 수 있다.

👇 먼저 아래 코드를 통해 두 라이브러리의 기본적인 사용 예시를 살펴보자.

SWR

import useSWR from "swr";

const App = () => (
  <div>
    <SWRProfile />
  </div>
);

const SWRProfile = () => {
  const {data, error} = useSWR("https://61b88c9d64e4a10017d19053.mockapi.io/user", url =>
    fetch(url).then(res => res.json())
  );

  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;

  return <Profile library="SWR" data={data} />;
}

const Profile = ({library, data}) => (
  <div>
    <h1>Users from {library}</h1>
    {data.map(user => <p>{user.level} developer <strong>{user.name}</strong></p>)}
  </div>
)

export default App;

React-Query

import { QueryClient, QueryClientProvider, useQuery } from "react-query";

const queryClient = new QueryClient();
const url = "https://61b88c9d64e4a10017d19053.mockapi.io/user";

const App = () => (
  <div>
    <QueryClientProvider client={queryClient}>
      <ReactQueryProfile />
    </QueryClientProvider>
  </div>
);

const ReactQueryProfile = () => {
  const { isPending, error, data } = useQuery({
    queryKey: ["users"], 
    queryFn: () => fetch("https://61b88c9d64e4a10017d19053.mockapi.io/user").then(res => res.json())
  });

  if (error) return <div>failed to load</div>;
  if (isPending) return <div>loading...</div>;

  return <Profile library="React Query" data={data} />;
}

const Profile = ({library, data}) => (
  <div>
    <h1>Users from {library}</h1>
    {data.map(user => <p>{user.level} developer <strong>{user.name}</strong></p>)}
  </div>
)

export default App;

위 코드를 통해 알아볼 수 있는 SWR의 강점은 다음과 같다.

Provider

SWR은 별도의 Provider 없이 컴포넌트에서 바로 사용할 수 있으나, React-Query는 기본적으로 컴포넌트를 감싸는 별도의 Provider가 필요해 이를 설정해주어야한다. 사실 이게 귀찮게 다가오는 정도는 아니지만, 초기 설정을 하나 더 해주어야한다는 점이 있다.

Fetcher

useSWR, useQuery 모두 두 번째 인자로 fetcher를 받는다. 이때 SWR의 경우 첫 번째 인자로 url을 받고, 두 번째 인자인 fetcher에 첫 번째 인자로 받은 url을 넘겨주는 방식을 사용한다. 또한 SWR은 전역 설정을 통해 fetcher를 정해둘 수 있다. 그러나 React-Query는 fetcher에 url을 직접 전달해주어야 한다.


이외에 React-Query가 가지는 장점들에는 무엇이 있을까?

Devtools

React-Query에서는 공식적으로 react-query/devtools 를 통해 Devtool을 지원한다. 개발 모드에서만 사용하며, devtools를 통해 좀 더 확실하게 데이터의 흐름을 파악할 수 있다.

SWR 또한 devtools를 사용할 수 있으나, 서드 파티 라이브러리를 이용해야한다.

react-query devtools

무한 스크롤 구현

SWR과 React-Query 모두 무한 스크롤을 구현하는 데 필요한 기능들을 제공한다.

그러나 SWR로 무한 스크롤을 구현하려면 유저가 부가적인 코드를 작성해야하는 반면,

React-Query에는 getPreviousPageParamfetchPreviousPagehasPreviousPage 와 같은 다양한 페이지 관련 기능이 존재해 이를 이용해 무한 스크롤을 쉽게 구현할 수 있다!!

Selectors

React-Query에서는 select 키워드를 사용해 raw data로부터 원하는 데이터를 추출하여 반환할 수 있다. 아래 코드로 살펴보자.

import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery({
    queryKey: ["user"], 
    queryFn: fetchUser,
    select: user => user.username,
  })
  return <div>Username: {data}</div>
}

👆 위의 예시처럼 select 를 통해 원하는 데이터에 접근한 뒤 추출이 가능하다!

Data Optimization

SWR과 다르게 React-query는 쿼리가 업데이트될 때만 refetch를 진행한다. 또한 여러 컴포넌트에서 동일한 쿼리를 사용하는 경우 한번에 묶어 업데이트를 진행한다! 이를 통해 렌더링 퍼포먼스를 개선해준다.

Garbage Collection

React-Query는 지정된 시간(기본 5분)동안 쿼리가 사용되지 않는다면 자동으로 메모리 해제를 하는 Auto Garbage Collection을 통해 메모리를 관리해준다.

🔘 버전 선택

기존에 v3 => v4의 변경점을 주로 소개했는데, 현재 latest가 v5이므로 v4와 v5를 기준으로 재작성하였다.

🔄 v5에서의 변경점

React-Query v4에서는 패키지 이름부터 @tanstack/react-query로 변경되면서 React에만 국한되지 않은 범용 프론트엔드 라이브러리로 개선되었다. 즉 tanstack이라는 오픈 소스 소프트웨어 프로젝트 하위의 라이브러리인 것이다!! 공식문서도 tanstack에서 제공하고 있으니 헷갈리지 않도록 주의하자.

그 변경점을 중점적인 내용들만 다뤄보면 아래와 같다.

1. 라이브러리 이름 변경

라이브러리 이름이 변경되면서 import를 할 때 @tanstack/react-query로 진행해야한다. react-query라는 라이브러리는 더는 없다!!

2. 다시 v3의 형태로 돌아왔다

v3에서 첫 번째 인자에 구조분해할당을 통해 모든 인자를 넣어주던 형태가 v4에서 변경되었었는데, v5에서는 다시 v3의 구조로 돌아왔다. 물론 장단점이 존재하겠지만 나의 경우 이렇게 값을 대입해주는 것이 명시적으로 어떤 인자가 들어가는지 표시되어 더욱 좋은 방법이라고 생각한다.

그러나 v4에서 추가된 queryKey에 배열의 형태를 넣어주는 것은 동일하게 가져왔다. 이를 주의하자!

+ useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
- useQuery(['todos'], fetchTodos)

4. 이제 undefined 대신 Error를 반환해요!(v3 => v4 변경점)

비동기 함수들 중에는 query가 잘못된 경우 모종의 이유로 결과값이 undefined인 경우가 있다.

이전까지는 이러한 경우 그저 반환값 undefined를 그대로 우리에게 보내주었는데, 이제는 React-Query 자체적으로 이를 감지하고 이런 경우 API 호출을 Failed라고 표시하고 Error를 반환한다. 값이 없는 경우도 Error로 분기해서 보내준다니, 아주 편리한 기능이라고 할 수 있다!!!

이외에도 v5에서는 좋은 기능들을 많이 제공하지만, 나머지 기능들은 좀 더 깊이 있게 다뤄야 할 내용들이어서 간단한 것들만 적어뒀다. 더 공부가 필요하다면 tanstack/react-query를 직접 방문한 뒤 정독해봅시다!!


여기까지 SWR과의 대표적인 차이를 알아보았다. 당연히 SWR 또한 좋은 라이브러리이며, SWR만의 강점이 더 존재하겠지만 이번 시간에 소개하지는 않겠다. 다른 다양한 기능들을 알아보고자 한다면 SWR | React-Query를 확인해보자!!

지금까지 React-Query의 개념과 사용 이유를 알아보고, 간단한 예시와 SWR과의 비교를 진행해보았다!!

Reference

TanStack Query
react-query 개념 및 정리
[React-Query] 총 정리
React Query vs SWR

profile
eng) https://medium.com/@a01091634257

8개의 댓글

comment-user-thumbnail
2023년 4월 26일

깔끔한 아티클 잘 읽었습니다!:)
SWR만 사용해봤고, React-query를 이번에 새로 들어가는 프로젝트에서 처음 써보게 되었는데 이 아티클 통해서 잘 공부한 것 같아요!

저는 아티클을 읽으면서 fresh,stale의 상태를 담고 있는 어떠한 flag가 있는 건가...? 했는데 react-query에서 제공하는 devtool을 이용하면 react-query에서 받아온 데이터의 생명주기를 볼 수 있는데, fetching - fresh - stale - inactive -delete 순의 생명주기를 갖는다고 하네요:)

그리고 swr과 비교되는 react-query의 장점으로 무한스크롤을 언급해주셨는데, 제가 저번 앱잼 때 swr의 useSWRInfinite라는 훅을 이용해서 무한스크롤을 구현했었는데요! swr에서도 언급해주신 react-query의 getPreviousPageParam, fetchPreviousPage, hasPreviousPage 같은 기능들을 충분히 이용할 수 있어서 구현하기에 크게 불편하지 않다고 느꼈어요!
개인적으로 swr을 써봤을 때 리액트 쿼리의 1/4 정도로 번들 크기가 작고, react-query에서 제공하는 대부분의 기능을 사용할 수 있어서 꽤나 만족했답니다! enabled와 같은 옵션도 swr에서 제공하는 조건부 패칭으로 가능할 것 같네요!
(https://url.kr/vycng5 궁금하신 분덜 제가 쓴 swr로 무한스크롤 구현하기 아티클도 읽어보세요 헤헤 )

그런데 가비지 콜렉션 같은 기능은 swr에 없어서 굉장히 흥미롭게 봤어요! 아무래도 번들 크기가 큰 것이 단점으로 꼽히는 react-query 답게 캐시 데이터가 메모리에서 차지하는 공간을 관리하는 전략이 필요했을텐데 react-query는 가비지콜렉션으로 그 부분을 해결했다는 것이 인상깊었어요!

그리고 이번에 새로 들어가는 프로젝트에서 react-query 초기세팅을 하면서 처음에 @tanstack/react-query가 최신 버전인 줄 모르고 yarn add react-query를 일단 갈겼는데 아티클들을 찾아보니 @tanstack/react-query와 그냥 react-query가 모두 있어서 헷갈렸었거든요! 그런데 형근님 아티클 보고 둘의 차이를 알게 되어서 @tanstack/react-query로 다시 세팅했네요 ㅎㅎㅎ 감사합니다:)

좋은 아티클 감사해요! 실습도 화이팅입니다!💪🏻

답글 달기
comment-user-thumbnail
2023년 4월 27일

React-Query에 대해 정말 꼼꼼하게 정리해주셨네요! 덕분에 React-Query에 익숙하지 않은 저도 쉽게 이해하고 공부할 수 있었습니다!

특히 SWR과 비교하여 React-Query의 장점/ 단점을 설명해주신 부분이 잘 와닿았습니다! 무한 스크롤 구현 시 SWR에서는 유저가 부가저으로 코드를 작성해야하지만, ReactQuery에는 다양한 페이지 관련 기능이 있기 때문에 이를 사용하면 무한 스크롤을 보다 쉽게 구현할 수 있을 것 같다는 생각이 들었습니다. React Query를 사용해서 따로 무한 스크롤을 구현해봐도 좋을 것 같네용..!
또, React Qeury에서는 Auto Garbage Collection을 지원해줘서 지정된 시간동안 쿼리가 사용되지 않을 경우 자동으로 메모리가 해제된다는 점이 가장 신기하게 다가왔습니다!

React Query에 대해 조금 더 찾아보다가 카카오페이에서 React Query를 사용하고 편리해진 점에 대해 정리한 글을 봤는데요, Redux에서는 기본 원칙 준수를 위한 BoilerPlate 코드들이 필요하지만 React Query에서는 해당 코드드 부분이 감소하여 소스코드의 복잡도를 낮춰주었다고 합니다. 또한, 업무와 협업의 효율성을 위한 규격화된 방식을 제공해주며 사용자 경험 향상을 위한 다양한 Built-in 기능을 제공해주기에, ReactQuery를 사용하여 비동기 데이터 처리를 하고 있다고 합니다..! (더 자세한 설명은 https://tech.kakaopay.com/post/react-query-1/ 참고해보세용ㅎㅎ)

마지막으로, React Query 버전 선택과 해당 버전을 설치함에 있어서 변경되는 부분들까지 정말 자세하게 짚어주셔서 여러모로 정말 유익한 아티클이 된 것 같아요! 알찬 아티클 감사합니당~~

답글 달기
comment-user-thumbnail
2023년 4월 27일

리액트 쿼리에 대한 아티클 깔끔하게 정리해주셔서 감사합니다!

특히 리액트 쿼리의 캐싱은 서버의 콜을 줄여주게 되므로 서버의 과부하를 줄일 수 있다는 부분이 가장 잘 와닿았던 것 같습니다. 리액트 데브툴스에 대해서도 처음 알게 되었는데 캐시된 데이터와 쿼리 뮤테이션 등의 상태를 쉽게 확인할 수 있어 디버깅툴로도 정말 좋은 것 같습니다.

무한 스크롤이란 단어가 나와 찾아보았는데 페이지 이동없이 새로운 컨텐츠를 계속해서 볼 수 있도록 하는 것 같습니다. 마치 영화 사이트에 보면 영화를 스크롤로 계속 해서 보여주는데 사용되는 것으로 이해했습니다! 다음에 기회가 된다면 꼭 해보고 싶네요!

답글 달기
comment-user-thumbnail
2023년 4월 27일

아티클 정말 좋네요!!
프로젝트에서 React-query를 사용했었는데, useMutation을 이용한 이유 바로 get을 해오기 위해서 useQueryClient()를 사용할 수 있어요!
const queryClient = useQueryClient(); 이렇게 미리 선언해두고, mutate에 성공했을 때 queryClient.invalidateQueries("beatId"); 요런식으로 부르면, beatId를 unique key로 하는 useQuery를 실행한답니다!
요거는 제가 쓴 글인데, 비록 엄청 자세하진 않지만 전반적인 코드 내용을 담았구, 이 안에 참고자료들도 읽어보시면 좋을 것 같아요!(https://velog.io/@seojisoosoo/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC)
그리고 무한 스크롤의 경우는 useQuery로 구현할 수도 있지만, useInfiniteQuery를 사용하면 좀 더 원활하게 무한스크롤을 구현할 수 있어요, 제가 진행했던 프로젝트에서는 useQuery, useMutation으로 데이터를 바로바로 캐싱하면서도 무한스크롤을 구현해야했는데, 여기서 데이터 캐싱과 무한스크롤이 충돌하거든요! 그래서 useInfiniteQuery를 이용해서 무한스크롤을 구현하고, 캐싱이 필요할 때마다 랜덤키를 발급받아주는 방식을 이용했습니다!
또, 코드를 보시면, const {data} 요런식으로 캐싱하잖아요! 그런데 만일 한 컴포넌트에서 여러가지 패칭을 하게 된다면, const {data:name} 이런식으로 직접 네임을 지정할 수도 있습니다!

답글 달기
comment-user-thumbnail
2023년 4월 27일

글이 쑥쑥 읽히네요 흥미로웠어요!
글을 읽으면서도, 이전에 react query를 사용하면서도 staleTime에 대해 느낀 의문점이 있었어요.

staleTime은 일정 시간 동안 fresh 상태를 유지해 refetch 트리거가 발생하더라도 refech가 일어나지 않도록 해주는 옵션인데, 항상 최신의 데이터를 유지하는 것이 좋은게 아닌가 생각했거든요! 또, 실제로 짧은 시간 동안 서버에서 데이터가 바뀌었는데 staleTime에 의해 최신 데이터를 보여주지 못하는 것은 좋지 않은 것 같다는 생각이 들었어요.

그래서 대체 어떤 때에 사용하기 위해 만들어진 옵션인가 궁금해서 찾아보았더니, 자주 변경되는 데이터라면 지정하지 않는 편이 좋지만, 정적인 데이터 또는 자주 변경될 필요가 없는 데이터라면 staleTime을 지정해서 서버의 부담을 줄여주는 것이 좋다고 하네요. ㅎㅎ

그래서 찾아보니 항상 전역적으로만 설정하는 것이 아니라, useQuery에서 특정 쿼리에만 다른 staleTime을 줄 수도 있다고 해요! 적절한 staleTime을 부여하는 것도 도움이 될 것 같네요 ㅎㅎ
이렇게 적절하게 부여해야 캐싱 기능을 제대로 활용할 수 있다고 합니다!

또, cacheTime에서 새로운 데이터를 fetch 해오는 동안 stale data를 임시로 보여준다는 것도 서버에서 변경이 된 데이터라면 낡은 데이터를 보여주는 것이 과연 좋은가? 라는 의문점이 들었는데요.

리액트 쿼리는 아무런 데이터도 UI에 표현하지 않는 것보단 최신 server state를 받아올 때까지 stale data라도 view에 표현해주는 전략을 취한거라고 하네요! 이는 더 좋은 UX로 이끌어가는 방향이 될 수 있다고 합니다.

좋은 아티클 감사합니다! :)

답글 달기
comment-user-thumbnail
2023년 4월 27일

좋은 아티클 감사합니다!
react-Query와 SWR을 예시코드를 보여주면서 설명해주셔서 이해가 더 쉽게 된 것 같아요!
특히 React-Query가 데이터를 캐싱하고 cacheTime으로 데이터를 유지한다는게 정말 유용하게 사용할 것 같다는 생각입니다.

저는
1. staleTime이 fresh에서 stale로 변경되는데 걸리는 시간이 왜 필요한지와
2. Server 데이터를 불러오는 과정에서 구현해야할 추가적인 설정들을 진행할 필요가 없다는 이야기이다. 추가적인 설정이 무엇인지고 어떻게 그 부분을 react-query로 어떻게 사용하는지와
3. 왜 client와 server데이터를 분리해야하는지에 대해서 궁금해서 한번 공부해봤어요

  1. 처음 생각으로는 mount가 되면 당연히 데이터를 refresh해야된다고 생각했는데 정적인 데이터 또는 자주 변경될 필요가없는 데이터라면 서버의 부담을 줄여주기 위해 사용한다고 하네요.

staleTime을 사용하면 unmount후 mount가 발생하여도 staleTime이 지나지 않았으면 fetch를 발생시키지 않는 방식으로 적용할 수 있을 것 같습니다.
유의할 점은 staleTime이 cacheTime보다 길더라도 cacheTime이 지나면 데이터가 사라지기 때문에 적용할 때 staleTime보다 cacheTime을 길게 설정해야 할 것 같습니다.

  1. 공부하면서 확인해봤는데 저희가 보통 redux를 쓰거나 redux-toolkit을 쓰면 error나 loading을 boolean값으로 두고 관리를 하는데 이런 추가적인 설정 없이 onSuccess와 onError로 관리할 수 있다는 장점이 있고, React Component 내에서 자연스럽게 서버의 데이터를 사용할 수 있다는 장점이 있는 것 같아 매력적인 것 같습니다.

3.마지막으로 server data와 client data를 분리하면 어떤 점이 좋은지 알아봤는데요

데이터 분리시 필요한 데이터만 가져와서 렌더링할 수 있어 성능 향상을 발생시킬 수 있고, 서버와 클라이언트 데이터의 충돌을 예방하고 애플리케이션 일관성을 유지할 수 있습니다. 또 두 데이터가 썩이면 코드가 복잡해지고 유지보수가 어려워지는데 React-Query는 데이터를 중앙 집중적으로 관리해서 유지보수성을 향상시킬 수 있다고 합니다.

[https://tech.kakaopay.com/post/react-query-1/#user-content-fn-2][https://bum-developer.tistory.com/entry/React-React-Query-QueryClient-stale-cacheTime]

답글 달기
comment-user-thumbnail
2023년 4월 27일

깔끔하게 정리해주셔서 react-query에 대한 전반적 흐름을 이해할 수 있어 좋았습니다!

앞에 많은 분들이 제 궁금점을 해결해주셔서 저는 간단하게 추가적인 내용을 더 찾아봤는데요! react-query를 사용하다보면 여러 플래그 값들로 인해서 의도치 않은 리렌더링이 발생할 수 있는데 이때 staleTime 을 설정해서 refetch가 진행되지 않게 하더라도 리렌더링이 발생하게 되면 notifyOnChangeProps를 통해 최적화를 하면 된다고 하네요!

이것을 notifyOnChangeProps: 'tracked',게 해서 사용하면 tracked 로 설정해주면 리액트 쿼리는 렌더 중에 사용 중인 속성을 알아서 추적하고 사용 중인 속성들이 변화가 있을 때에만 리렌더링해주기에 최적화를 할 수 있다고 합니다! 자세한 예제와 함께 확인해보기실 원한다면 https://velog.io/@kimhyo_0218/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94 요기도 한 번 봐보면 좋을 것 같아요 ~!

좋은 글 감사합니다 ~

답글 달기
comment-user-thumbnail
2023년 5월 5일

깔끔한 아티클 잘 읽었습니다!!

react-query는 관심만 가지고 있고 제대로 사용해본적은 없었는데 아티클을 읽으니 한번 제대로 사용해보고 싶어요!
react-query의 주요기능인 caching과 caching에 관련된 staleTime,cacheTime에 대한 글 너무 좋았습니다!

또한 react-query에서도 정말 다양한 기능이 있는데 그 중에서도 많이 사용된다고 생각하는 기능들을 코드와 함께 보여주셔서 이해가 쏙쏙 가네요! 버전에 따른 차이 부분도요!
데이터 페칭 라이브러리하면 생각하는 react-query 와 swr과의 비교도 정말 잘 읽었습니다!

고생하셨습니다~~!

답글 달기