react-query란 도대체 뭘까?

정도영·2024년 5월 1일
24
post-thumbnail

저는 React Query를 처음 접해보는 사람의 입장으로서, 조금 더 쉽게 이에 대해서 이해하고, 사용해야 하는 이유를 느껴보기 위해서 이 아티클을 작성합니다.

React Query는 대표적인 서버 상태 관리에 사용되는 도구입니다!

그렇다면, React에서는 기존에 어떻게 상태관리를 하고 있었을까요?

▶️ React의 상태 관리

📚 state(상태)란?

저는 지난 주 공유과제를 하면서 React의 상태에 조금 더 이해할 수 있었는데요!
리액트에서 상태란 컴포넌트 내부에서 변할 수 있는 값입니다.

위 사진은 팜스프링의 사진인데요,
우측 상단에 로그인 한 상태로그인 하지 않은 상태에 따라 버튼이 구분되는 것을 확인할 수 있습니다.

즉, 상태"유저와의 상호작용을 위한, 변할 수 있는 데이터" 라고 다시 한 번 정리할 수 있습니다.

그럼 본격적으로 react-query에 대해서 알아보겠습니다.

▶️ react-query?

react-query란 fetching, caching, 서버 데이터와의 동기화를 지원해주는 라이브러리 - 공식문서

조금 자세하게 말하면, react-query는 React Application에서
서버의 상태를 불러오고, 캐싱하며, 지속적으로 동기화하고 업데이트하는 작업을 도와주는 라이브러리입니다.

그럼 여기서 나오는 서버의 상태는 위에서 알아본 React의 상태와 다른 걸까요?

서버는 클라이언트에게 데이터베이스에 있는 정보를 전달해주는 역할을 하는데요,
이 때, 데이터베이스에서 가져온 데이터
즉, 서버에서 전달받는 데이터를 우리는 서버의 상태라고 이야기합니다.

즉, 상태(state) 라고 하면 위에서 알아본 것 처럼, 클라이언트에서 뿐만 아니라 서버에서도 받을 수 있는 모든 것들을 포함하고 있습니다.

그래서 상태는 클라이언트의 상태서버의 상태로 나뉘어지는데,
각각의 간단한 특징에 대해 살펴보겠습니다.

클라이언트 상태

UI 테마, 사이드바, 폼 입력 등
즉, 유저와의 인터렉션에 의해서 발생하는 상태(데이터)

서버 상태

유저 DB 정보 등과 같이 클라이언트가 서버로부터 받아오는 모든 데이터
즉, API 통신을 통해서 서버에서 받아온 데이터(상태)

결국, 서버 상태 관리에 사용되는 react-query란 간단하게 비동기 API 호출을 통해 불러오는 데이터들을 관리하기 위해서 사용되는 것입니다.

그럼 이제는 왜, 도대체 왜 API 호출과 같은 데이터들은 react-query를 사용해서 서버 상태 관리를 사용해야할까? 라는 궁금증이 생기기 마련입니다.

▶️ Why React Query?

이는 React-query를 사용했을 때의 여러가지 이점들을 얻을 수 있기 때문입니다.
한 번 살펴볼까요?

React Query의 이점

📌 1. 캐싱(cachcing)

캐싱이란 특정 데이터의 복사본을 저장하여 이후 동일한 데이터에 다시 접근할 때, 속도를 높이는 것을 말합니다.

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

이 과정이 중요한 이유는 API 호출 == 비용을 뜻하기 때문입니다.

카카오톡처럼 실시간이 중요한 서비스가 아니라, 공지사항과 같이 많아야 하루의 1개 정도의 업로드가 되는 불필요한 API 호출을 페이지가 들어갈 때 마다 한다면, 우리는 불필요한 데이터 호출을 반복적으로 해 서버의 대한 부하와 비용을 늘리고 있는 것 아닐까요?

여기서 우리의 의문은 "반복적인 데이터가 아닌 데이터가 변경되었을 경우에는 어떻게 갱신할 수 있지?"입니다.

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

그럼 이런 에러를 발생시키지 않고, 필요한 상황에 적절하게 데이터를 갱신해 줄 수 있어야 합니다. 그렇다면 그런 상황은 언제일까요?

📚 언제 데이터를 갱신해야 하는가?

기본적으로는 컴포넌트 최초 mount 시에 데이터를 fetching 해온 후,

  • 새로운 Query Instance가 마운트 될 때 (refetchOnMount)
  • 브라우저 화면을 다시 focus 할 때 (refetchOnWindowFocus)
  • 인터넷이 재연결되었을 때 (refetchOnReconnect)
  • refetchInterval이 설정되어있을 때

위 조건에 따라 refetching이 발생하게 됩니다.
이를 위해서 React Query에서는 기본적인 아래의 옵션을 제공합니다.

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

위의 옵션들을 통해 React Query가 어떤 시점에 데이터를 refetching 할지 설정해줄 수 있습니다.

📚 staleTime vs gcTime(cacheTime) 이란?

v5 이후 cacheTime -> gcTime으로 바뀌었습니다.
이해가 쉽도록 cacheTime이라는 용어를 사용해서 설명하도록 하겠습니다.

여기서, cacheTime은 cache에 사용되는 시간이라는 것을 유추할 수 있는데,
저와 같이 처음 접해시는 분들은 'stale은 무슨 개념이지?' 라는 생각이 들텐데요

React Query에는 cache와는 별도로 stale이라는 개념이 있습니다.
그리고 stale과 대비되는 fresh라는 개념도 있습니다.

여기서 fresh와 stale 단어의 대비에서 알 수 있듯이
fresh는 데이터를 그대로 사용해도 좋을 만큼 신선한 상태라는 뜻이며,
stale은 데이터를 새롭게 fetch해 오는 것이 필요할 만큼 신선하지 못한 상태라는 뜻입니다.

중요한 개념인데요, staleTime을 설정해주면 그만큼 데이터가 fresh 상태로 존재하였다가 그 후에는 stale 상태로 바뀝니다.

예를들어, staleTime을 10,000으로 설정하였다면, data fetching이 성공한 후 10초(= 10,000ms) 동안 fresh 상태로 존재하다가 10초 이후에는 stale 상태가 됩니다. stale 상태가 된 후 위에서 알아본 refetching 특정 조건들이 충족되면 이때, refetching이 발생하게 됩니다.

주의할 점은 stale 상태가 되었다고 해서 refetching이 곧바로 일어나는 것이 아니라,
stale 상태가 되고 특정조건을 만족해야 refetching이 일어난다는 것입니다.

📚 그럼 왜 stale이 필요할까?

저도 처음에는 잘 이해가 되지 않았습니다.
stale과 cache는 비슷한 역할을 하는 것 같은데 어째서 cache만으로 관리하지 않고, stale과 cache라는 개념으로 나누어서 관리하지? 라는 의문이 들었습니다. 그런데 cache만으로 관리했을 때와의 차이점을 생각해보니 조금 이해가 편했습니다.

위 상황은 useQuery를 이용해 캐싱된 데이터가 stale 상태로 바뀐 이후에,
페이지를 이동하여 동일한 키값의 데이터를 refetching 해야하는 상황을 나타낸 것입니다.

stale과 cache로 관리할 때cache만으로 관리할 때의 ⓐ, ⓑ, ⓒ, ⓓ는 각각 다음과 같은 모습입니다.

차이점은 ⓒ입니다.
자료의 위 사진들처럼 stale과 cache로 관리하게 된다면, ⓒ에서 stale 상태의 캐싱된 데이터를 이용한 UI를 먼저 보여준 후, refetching을 완료하면 새로운 데이터를 이용한 UI로 교체해줄 수 있습니다.

refetching 이전과 이후의 데이터가 많이 다르지 않다면, 사용자는 페이지 이동 후 더욱 빠르게 서비스를 이용할 수 있게됩니다.

반면 cache만으로 관리하게 된다면 이와는 달라집니다.
자료의 아래 사진 처럼 페이지 이동 직전에 캐시가 만료되었다고 가정해보겠습니다.
캐시가 만료되어 삭제되었을테니, ⓒ에서 refetching이 완료되기 전까지 데이터가 없으므로 Loading 페이지를 보여줘야합니다. 그리고 refetching이 완료되면 그제야 데이터를 이용한 UI를 보여줄 수 있습니다.

이렇게 되면 사용자는 페이지 이동 후 더욱 느리게 서비스를 이용하게 됩니다.

그래서 'staleTime이 cacheTime보다 작게 설정하는게 좋다' 라고 합니다.

그래서, staleTime, cacheTime은 원격 서버와 통신하는데에 걸리는 시간을 단축하고자,
여러번 불러올 데이터는 캐시에 저장하여 반복해서 필요로 할 경우 원격 서버가 아닌 캐시에서 빠르게 가져올 수 있는 방식입니다!

📚 Query Invalidation

여기서 지정해준 staleTime과는 무관하게 즉시 데이터를 stale 상태로 처리해야하는 경우가 있는데요!

예를 들어, API에 POST 요청을 보내 데이터 값을 업데이트할 때, API 엔드포인트에 있는 데이터의 값캐시에 저장되어있는 값보다 더 최신 상태가 되고, 캐시에 저장되어있는 데이터는 곧바로 stale한 값이 되어버립니다.

이럴 때엔 즉시 캐시 데이터를 stale 상태로 처리해줘야 합니다.

이는 QueryClient 객체의 invalidateQueries 메소드를 활용해서 수행할 수 있습니다!

// 사용법

// useQueryClient 훅을 사용하여 queryClient 객체 생성
const queryClient = useQueryClient();

// 캐시의 모든 쿼리 무효화
queryClient.invalidateQueries();

// 'users'로 시작하는 키가 있는 모든 쿼리 무효화
queryClient.invalidateQueries({ queryKey: ["users"] });

이처럼 간단하게 캐시 데이터를 stale한 상태로 바꿔, 데이터가 바뀌었다면, 조건에 따라서 refetching이 수행되게 할 수 있습니다.

📌 2. 로직의 단순화

📚 Data Fetching

기존에 서버에서 데이터를 가져오기 위해서는 다음과 같은 절차가 필요했습니다.

  1. Fetching을 위한 코드 작성
  2. 데이터를 담아 둘 상태 생성
  3. useEffect를 이용해 컴포넌트 Mount시 데이터를 Fetching 한 후에 상태 저장

이를 코드로 작성하면, 아래처럼 작성했었습니다.

// getData 함수는 서버에서 게시물을 반환하기 위한 비동기 함수를 나타냅니다.

function App() {
  const [state, setState] = useState([]);
  
  useEffect(() => {
    getData()
      .then((dataList) => setState(dataList))
      .catch((e) => setState([]));
  }, []);
  
  return <div>{JSON.stringify(state)}</div>;

하지만, React-query를 사용하면 다음과 같이 바꿀 수 있습니다.

function App() {
  const { data } = useQuery(["data"], getData);
  
  return <div>{data}</div>;

서버 데이터를 담을 상태(state)를 만들고, useEffect로 상태에 데이터를 담는 5줄이 넘는 코드를 useQuery를 이용해 단 한 줄로 처리할 수 있습니다. 미쳤다!

📚 동기적 실행

또, 특정 API를 호출하기 위해 다른 API의 값을 파라미터로 넣어줘야 할 때가 있습니다.

그럴 때는 API를 동기적으로 호출해줘야 합니다.

// API를 동기적으로 호출 예시
const [state1, setState1] = useState();
const [state2, setState2] = useState();

useEffect(() => {
  getData().then((dataList) => {
    setState1(dataList[0]);
  })
}, []);

// state1의 데이터를 파라미터로 넣어 호출하는 API
useEffect(() => {
  if(state1) {
    getAfterData(state1).then((dataList) => {
      setState2(dataList);
    })
  }
}, [state1]);

API 호출 위한 조건이 추가되어 동기적으로 관리가 필요하고, useEffect의 위치가 다른 코드들과 섞인다면 어떤 데이터가 언제, 어떻게 호출되는지 흐름과 시점을 파악하기 어렵습니다.

하지만, React-query의 enabled 옵션을 사용하면 보다 깔끔하고 간단하게 대체할 수 있습니다.

const { data: data1 } = useQuery(["data1"], getData);
const { data: data2 } = useQuery(["data2", data1], getData,
  { enabled: !!data1 }
));

enabled는 조건이 충족될 때만 API를 호출하게 되어 위와 같이 간단하게 API를 동기적으로 호출할 수 있습니다.

📌 3. 프로젝트의 규모

프로젝트의 규모가 커지고 관리해야 할 데이터가 많아지면, Client에서 관리하는 상태(데이터)Server에서 관리하는 상태(데이터)가 분리되야할 필요성을 느끼게 됩니다.

  • 클라이언트 상태: 모달 관련 데이터, 페이지 관련 데이터 등등
  • 서버 상태: 사용자 정보, 비즈니스 로직 관련 정보 등등

위에서 알아봤듯, 이렇게 클라이언트의 상태와 서버의 상태가 나뉘어질 수 있는데요.
실제 클라이언트 상태의 경우 Redux, Recoil와 같은 전역 상태 관리 라이브러리를 통해 잘 관리되어오고 있으나, 문제는 이러한 라이브러리들이 서버 상태까지도 관리를 해야 하는 상황이 발생한다는 것입니다.

이런 라이브러리 외에도 비동기 처리 로직이나, 서드 파티 라이브러리를 지원하는 것들이 많이 있지만, 클라이언트의 상태와 서버의 상태를 완벽하게 분리하여 관리에 용이하도록 충분한 기능을 지원하는 것은 어렵습니다.

즉, 이런 것들은 클라이언트의 상태를 관리하는데 로직이 집중되어 있기 때문에, 서버의 상태까지 효율적으로 관리하기에는 한계가 있습니다.

이때, React Query는 이런 문제에 대한 해결책을 제시할 수 있습니다.

// 클라이언트의 상태
const [toggle, setToggle] = usestate(false);

// 서버의 상태
const { data } = useQuery(["data"], getData);

이 예시에서는 컴포넌트 내부에서 위와 같은 로직을 통해 서버의 데이터를 가져오고 있는데, 이는 서버의 데이터를 불러오는 과정에서 구현해야할 추가적인 설정들을 진행할 필요가 없어졌음을 말해줍니다.

클라이언트의 상태는 상태 관리 라이브러리 혹은 state로 관리하고,
서버의 상태(데이터)는 React-query가 관리할 수 있습니다.

이를 통해 클라이언트 상태와 서버의 상태를 온전하게 분리할 수 있습니다.

▶️ 사용법 맛보기!

React query에서 data fetching을 위해 제공하는 대표적인 기능에 대해 알아보겠습니다.
기본적으로 GET에는 useQuery, PUT, UPDATE, DELETE에는 useMutation이 사용됩니다.

📌 useQuery

// 사용법
const { isLoading, error, data } = useQuery( {queryKey: [ ‘key’ ], queryFn: callback, options} );

useQuery()는 필수 인자로 queryKey, queryFn 을 받고, 선택적으로 options을 받습니다.

queryKey

  • 쿼리의 고유 키
  • React query 최신 버전 부터는 배열 표기법을 사용해서 키 지정

queryFn

  • 훅 호출시 실행되는 Promise 반환하는 함수
  • 해당 callback 함수에서 데이터 fetching

options 기본적으로 많이 사용하는 옵션들은

  • enable 데이터 자동 패치 여부
  • retry 데이터 재요청 여부 및 횟수
  • staleTime 데이터가 fresh 상태로 유지되는 시간 (설정 시간이 지나면 stale)
  • cacheTime inactive 상태인 캐시 데이터가 메모리에 남아있는 시간 (해당 시간이 초과되면 가비지 컬렉터에 의해 메모리에서 제거됨)
  • refreshOnMount, refetchOnWindowFocus, refetchOnReconnet 쿼리가 stale 상태인 경우 refetch 실행 여부

options에는 정말 유용한 옵션들이 많으니 공식문서를 참고하면 좋을 것 같습니다.

// 사용 예시
const getUsers = () => fetch( 'https://jsonplaceholder.typicode.com/users')
                          .then( res  => res.json() )

const query = useQuery( { queryKey: [‘users’], queryFn: getUsers })

그리고 useQuery() 훅의 리턴값에는 쿼리의 상태 정보를 담고 있는 객체가 들어가는데요!

const { isLoading, isError, data, error, refetch, remove } = useQuery( { queryKey: [‘todos’], queryFn: getTodos });

대표적인 프로퍼티

  • isLoading 쿼리가 현재 로딩 중인지 여부 (boolean)
  • isError 쿼리 결과가 오류인지 여부 (boolean)
  • data 쿼리를 통해 성공적으로 fetching된 데이터
  • error 쿼리 실행 중 발생한 모든 에러
  • refetch 쿼리 데이터를 수동으로 refetch 하는 트리거 메소드
  • remove 캐시에서 특정 쿼리 제거하는 메소드

이 훅을 이용해서 쉽게 데이터를 fetching하고, 캐싱하고, 데이터를 가져오는 동안의 여러 상태도 편하게 관리할 수 있습니다.

📌 useMutation

// 사용법
const mutation = useMutation({ mutationFn: mutationFunction });

mutationFn

  • 실행하고자 하는 함수를 반환하는 함수
// 사용 예시
const AddUser = useMutation({
  mutationFn: ( user ) => {

    return fetch('https://jsonplaceholder.typicode.com/users',{
      method:'post',
      headers: {
        "Content-Type": "application/json",
      },
      body:JSON.stringify( user )
    }).then( res =>  res.json() )
  })

그리고 useMutation() 훅의 리턴값에는 마찬가지로 실행 결과와 관련 정보를 담은 객체가 들어가는데요!

대표적인 프로퍼티

  • isLoading 뮤테이션이 현재 로딩 중인지 여부 (boolean)
  • isSuccess 뮤테이션 결과가 성공인지 여부 (boolean)
  • isError 뮤테이션 결과가 오류인지 여부 (boolean)
  • data 뮤테이션 실행 후 반환된 데이터 (있을 때만)
  • mutate 해당 뮤테이션 실행을 위해 호출할 메소드. (인자에 담을 변수를 객체로 전달)
  • reset 뮤테이션 초기 상태로 리셋하는 메소드
  • onSuccess 뮤테이션 성공시 호출할 콜백함수
  • onError 뮤테이션 실패시 호출할 콜백함수

▶️ 회고

이번 아티클을 통해서 모든 react-query의 유용한 옵션들과 다양한 기능들에 대해서 다룰 순 없어서 너무 아쉽지만, 아티클과 실습을 준비하며 여러 이점들을 알 수 있었습니다.

그리고 이번에 react-query를 처음 접하면서 제 개인적인 생각으로 다음 프로젝트에는 무조건 도입해야겠다는 생각이 들었습니다.

그 이유는 다음과 같습니다.

  • 옵션 하나로 간편하게 캐싱 기능 및 이에 따른 API 호출 비용 감소
  • 코드의 간소화
  • 규격화된 방식으로 코드 작성 가능

등... 사용해야할 이유는 정말 많은 것 같습니다.

그리고, 캐싱에 대해서도 정말 많이 생각할 수 있었습니다.
캐싱이 필요한 여러가지 상황 가정을 통해서 정말 실시간이 중요한 게 아니라면, 어느정도까지 캐싱을 제공해야할지도 여러 궁금증이 생겼네요... ㅎ

두서없는 긴 글 읽어주셔서 감사합니다.

다 못다룬 정말 유용한 이점인 낙관적 업데이트에 대해서도 꼭 한 번 읽어보시길 바랍니다.

참고

profile
대한민국 최고 개발자가 될거야!

9개의 댓글

comment-user-thumbnail
2024년 5월 2일

우와 아티클 너무 잘 써주셨네요 !!!! 읽으면서 어라 이게 뭐지? 싶을 때 바로 다뤄주는 걸 보면서 공부 방향과 고민의 흐름이 너무 잘 보였던 것 같아요.

저는 앱잼을 하면서 리액트쿼리를 처음 써봤었는데, 에디터 라이브러리 커스텀을 하느라 깊게 다뤄보지 못하고 미리 구현되어 있던 부분들을 보면서 사용을 하게 되어서 리액트쿼리에 대한 전반적인 개념이 잘 잡혀있는 상태는 아니였어요. 도영님의 아티클을 읽으면서 내가 사용했던 부분이 이런 부분이었구나 ~ 하면서 읽었습니다 !

마일의 경우에는 에디터 뷰에서 서버의 상태값과 클라이언트의 상태값을 분리해야하는 로직이 필수적이였어요. 글 임시저장, 글 최초 제출, 임시저장한 글 이어쓰고 제출하기, 글 수정하고 제출하기, 이미 임시저장된 글이 있는데 새로운 글을 임시저장할 경우 등등 .. 서버와 클라이언트단의 상태를 분리하여 구현하지 않으면 정말 엉망진창인 에디터가 만들어지는 경험을 .. 했답니다. (그래서 useEffect가 남발하는 코드를 작성하게 되었고 리팩토링 과정에서 리액트쿼리의 순기능을 정말 많이 맛보며 짜릿하게 리팩을 한 경험이 있습니다 .. )

도영님 아티클을 읽으며 궁금했던 부분에 대해서 정리를 해보았어요 !
1. enabled에 느낌표 두개가 들어가는 이유
!!가 뭔지 제대로 모르고 사용하고 있었는데 이번기회에 알아보게 됐네요... !!는 무조건 bool값을 반환해주는 연산자로 값의 존재 여부를 확인해줄 때 사용된다고 합니다 !

저는 useQuery를 사용하면서 enabled 속성을 정말 유용하게 사용하고 있어요. useQuery의 경우에는 mutation처럼 함수를 원하는 곳에서 호출할 수가 없고 쿼리함수를 작성하는 동시에 api 요청이 보내져서, 버튼을 눌렀을 때 useQuery를 사용하고 싶다면,
1. 해당 버튼이 눌렸는지 여부를 따지는 state를 정의하고
2. 해당 state값을 useQuery의 enabled 속성에 넣어주는 방식을 사용해서 원하는 곳에서 useQuery를 사용하는 방식으로 많이 사용하는 것 같아요!

이 부분은 궁금했던 부분은 아니고, 제가 실제 리액트쿼리를 쓰면서 유용하게 사용하고 있는 속성값에 대해서 정리해보았습니다.
1. useMutation에서 queryClientinvalidateQueries 사용하기
우선 queryClient란 캐시와 상호작용할 때 사용되는 클래스에요. 정말 다양한 속성이 있는데 그 중에서도 invalidateQueries를 가장 많이 사용하는 것 같아요.
예시를 하나 들어보자면
1. 작성된 글 목록 가져오는 GET 쿼리함수
2. 글을 작성하는 POST 쿼리함수
3. 작성된 글을 삭제하는 DELETE 쿼리함수
이렇게 세가지 함수가 있다고 가정하고 다음과 같은 유저 플로우를 생각해볼까요?
사용자가 글을 작성하고 나서, 해당 글을 삭제한 후에 글 목록 페이지에 가보면 어떻게 되어있을까요
별 다른 설정을 해주지 않았다면 해당 글 목록 페이지에 있는 값들이 캐싱되어 서버에 재요청이 가지 않은 상태이기 때문에 삭제된 글이 글 목록에 아직 남아있는 이슈가 발생할 수 있어요.
이때, 작성된 글을 삭제하는 DELETE 쿼리함수에서, onSuccess시에 작성된 글을 가져오는 GET 쿼리함수를 useClient의 invalidateQueries를 사용하여 다시 쏴준다면 글이 삭제됨과 동시에 글 목록도 최신화가 바로 진행되는 것을 볼 수 있답니다!

아래는 위 플로우에 대해서 실제 마일에서 작성되고 있는 코드예시입니다!

// 글 삭제
export const useDeletePost = (postId: string, topicId: string) => {
  const queryClient = useQueryClient();
  const data = useMutation({
    mutationKey: [QUERY_KEY_POST_DETAIL.deletePost, postId],
    mutationFn: () => deletePost(postId),
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ['getArticleList', topicId],
      });
    },
  });

  return data;
};

+) 리액트 쿼리를 커스텀훅과 axios와 같이 사용해서 구현을 하는 것도 공부해보시면 좋을 것 같아요 !! 마일에서는 쿼리를 사용할 때 axios로 요청을 보내는 api 로직을 작성하고, 커스텀 훅에서 리액트쿼리를 사용하여 받아온 서버값들을 어떻게 관리하고 업데이트하고 캐싱할지 작성을 하면 로직을 보기가 편리하더라구요!
+) queryClient의 속성들에 대해서도 더 공부해보면 좋을 것 같아요!

좋은 아티클 작성해주셔서 감사합니다! 특히 stale과 캐싱의 차이점에 대해서 다뤄주신 부분이 너무 인상깊었습니다! 너무너무 고생많으셨고 실습 기대할게요 ! 정도영 폼미미나~

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

리액트 쿼리의 이점을 배경 설명과 함께 해 주신 덕에 더 설득력이 있는 아티클이었어요! 기술 블로그를 참고하여 리액트 쿼리를 사용해본 경험만 있었는데, 덕분에 리액트 쿼리의 개념을 기초부터 차근차근 정립해 나갈 수 있는 기회였습니다 ◡̈ 특히 staleTime과 cacheTime 라는 옵션을 처음 접하게 되었는데 React Query가 데이터를 refetching 하는 시점을 설정할 수 있게 해 준다는 점에서 인상적이었던 것 같아요! 이렇게 꼼꼼하게 아티클을 잘 작성해 주셨는데 실습 시간은 또 어떨지 기대되네요! 작성하시느라 고생 많으셨습니다!

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

우선 저 또한 굉장히 관심 있었던 tanstack query에 대해 이렇게 자세하게 알려주셔서 정말 좋았던 것 같습니다. 고민했던 흔적이 많이 드러났던 것 같아요 !

사실 지금까지 useQuery를 거의 몇번 get 요청을 받아올 시에만 사용해보고, 제대로 사용해본 적은 없는 것 같습니다. 여타 다른 개발자분들의 코드를 보거나, 강의에서 사용법 정도를 접한 정도라 제대로 사용해보고 싶은 마음이 있었는데, 도영님께서 이렇게 이론과 맛보기까지 알려주시니 정말 좋네요.

리액트 쿼리는 정말로 서버 상태를 관리하기에 가장 좋은 툴이 아닐까 라는 생각이 드네요. 서버 상태 데이터를 패칭하고, 캐싱 또한 해주며, 동기화까지 해주니까 말이에요. 저도 최근에 조금 더 깊게 공부하면서 알게 된 것인데 useQuery, useMutation 뿐만 아니라 queryClient 객체 자체에서도 정말 중요한 메서드들이 많은 것 같아요. 그래서 그것도 한 번 다뤄봐주시면 아주 좋았을 것 같다는 조심스러운 피드백 한번 해봅니다. 왜냐면 마지막 쯤에 남겨주신 "낙관적 업데이트"를 탠스택 쿼리를 활용하여 구현하기 위해서는, queryClient에 대한 이해가 필수적이다라고 생각이 들어서요. 물론 시간에 여유가 없어 다 못다루신 점 당연히 이해가 갑니다!

statecache의 차이점을 캐싱만으로 처리하였을 때와 비교하여 다뤄주신 것 정말 감명 깊었습니다. 좋은 아티클 감사합니다 ㅎㅎ 합동세미나도 화이팅 !

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

지난 웹잼에서 저희 팀도 리액트 쿼리를 도입했었고, 그 때 처음 사용해봤는데요.
다른 팀원들은 모르겠지만, 저는 남들이 다 쓰고 좋다고 하니까 썼던 기억이 있습니다.
그래서 리액트 쿼리를 왜 사용해야되는지, 어떤 면이 좋은지 제대로 알지 못했습니다.
뿐만 아니라 제가 리액트 쿼리를 사용하면서 아쉬웠던 부분도 있습니다.
우리 팀이 리액트 쿼리를 과연 잘 사용하고 있는게 맞나?
axios 쓰는 것과 무슨 차이지?
우리가 리액트 쿼리를 axios처럼 사용하고 있는 것은 아닌가?
등등등 생각이 들었는데요,
도영님 아티클 읽고 정리가 되는 느낌입니다.
이번 기회에 리액트 쿼리 제대로 이해하고 사용해보겠습니다.

사실 낙관적 업데이트라는 말을 처음 들어 궁금해서 정리해봤습니다.
낙관적 업데이트란 서버로부터 응답을 받기도 전에 UI에 표시되는 데이터를 업데이트 하는 것이라고 하네요.
기존에는 쿼리의 캐시가 업데이트 될 때까지 실제 페이지에서 업데이트 되지 않는데,
낙관적이라는 말 그대로 mutation이 이미 성공한 것처럼 데이터를 보여주는 것이라고 합니다.
사용자에게 더 좋은 경험을 선사할 수 있다는 장점이 있을 것 같네요.

도영님 덕분에 리액트 쿼리의 매력에 빠지게 될 것 같습니다.
아티클 정말 잘 읽었습니다.
감사해요 ㅎㅎ!

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

query를 사용해보긴 했는데, 시식? 느낌으로 맛만 본 정도로 사용해봤었는데, 기본 개념부터 공부하면서 생기는 질문이나 의문점들을 너무 잘 정리해주고 읽기 쉽게 글을 작성해주셔서 이해하기 쉬웠던 것 같습니다. 너무 감사합니다.

stateTime 이라는게 있고, 이를 기준으로 데이터를 refetching 한다고 알고있었는데, 그렇게 단순한게 아니었네요,,
cacheTime에 대해서도 새롭게 알게되고, 이 둘의 차이점에 대해서도 제대로 이해하게 됐습니다.
stale과 cache에 관한 사진은 또 어떻게 저렇게 딱 이해하기 쉬운 자료를 준비하셨는지 해당 내용 읽으면서 도움이 정말 많이 됐습니다.

개인적으로 제가 query를 사용하면서 ‘와 이거 진짜 좋다’ 라고 느꼈던 부분은 useQuery와 useMutation의 리턴 값에 있는 isLoading 이었습니다.

if (isLoading) {
  return <SpinnerView />;
}

return (
	<View />
);

이와 같이 사용함으로써 데이터가 로딩중일 때 로딩화면을, 로딩이 완료된 후에는 받아온 데이터를 정말 쉽게 화면을 보여줄 수 있다는게 매력적인 부분이었던 것 같습니다.

아티클을 읽으면서 query의 기능을 제대로 활용하지 못하고 있다는 생각이 많이 들었습니다. 저도 더 공부해보면서 다음 프로젝트나 앱잼에서 query를 멋지게 쓰는 사람이 되고싶네요. 좋은 아티클 감사합니다. 고생하셨습니다!

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

이전 프로젝트에서 캐싱을 위해 리액트 쿼리를 썼던 적이 있는데, 번역 API를 호출하기 위해 서버에서 번역할 데이터를 가지고 왔어야 했었습니다. 이때 enabled 옵션을 이용해 체이닝해 API를 동기적으로 호출했던 적이 있는데, 확실히 더 깔끔하고 알기 쉽게 작성할 수 있어 좋더라구요!
서버랑 클라 상태를 분리해서 관리할 수 있는 점도 굉장히 매력적인 것 같습니다
React Query Devtools를 이용하면 GUI로 현재 데이터들의 상태를 확인할 수 있어서 더 편리했던 것 같아요!

도영님 아티클 덕분에 다음에 사용할 때에는 리액트 쿼리 더 잘 알고 사용할 수 있을 것 같네요!
정성스러운 아티클 감사합니다! 잘 읽었습니다 ㅎㅎ!

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

저는 react-query 강의영상을 통해 처음 접했었는데 그때 당시에는 제가 직접 서버데이터를 처리하는 로직을 짜고 상태를 관리해본 경험도 없이 그냥 개념을 공부했던 터라 어렵기만 하고 재미도 없어서 도중에 손을 놔버렸는데 도영님의 아티클을 읽고 정말 개념이 머리속에 쏙쏙 박히는것 같아요. 예전과는 달리 서버데이터를 처리하는 로직을 짜보고 react-query 같은 도구의 필요성을 느꼈기 때문에 더욱 흥미롭게 본것도 사실이지만 글이 너무 잘 읽혔습니다.

우선 stale이란 개념을 처음알게 되었고 cache와의 비교설명을 통해 쉽게 이해한것 같습니다. 정말 중요하면서도 효율적인 개념이네요. 저도 다음에 react-query를 도입해보고 싶습니다.

중간에 글을 읽으면서 궁금한점이 생겼는데 클라이언트 상태와 서버 상태를 분리해야할 필요성을 느낀다는 말에 공감을 많이 했는데 대부분 redux, recoil등 여러가지 상태관리 라이브러리로 상태를 관리하고 있는것으로 알고 있습니다.
이 내용 아래로 오는 코드에서

// 클라이언트의 상태
const [toggle, setToggle] = usestate(false);

// 서버의 상태
const { data } = useQuery(["data"], getData); 

이런식으로 상태를 분리하는것까지는 이해를 했는데 사실 redux, recoil같은 상태관리 라이브러리를 사용하는 이유가 전역상태를 효율적으로 관리하기 위함이라는것에 큰 가중치가 있다고 생각하는데 해당 코드는 단지 상태를 분리하는것에서 그치고 전역적으로 상태를 관리하려면 react-query를 사용하더라도 서버의 상태 또한 상태관리 라이브러리를 사용하여 저장해야하지 않나 생각이 들어 이 점이 궁금합니다!

도영님의 아티클을 읽고 react-query의 필요성을 크게 느꼈고 다음에 꼭 사용해보겠습니다. 감사합니다!

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

리액트 쿼리를 사용해본적이 없어서 어떤 개념인지 모호했는데 처음 보는 사람도 잘 이해할 수 있도록 아티클을 작성해주셔서 잘 읽을 수 있었습니다! 읽으면서 어?하는 부분이 있을 때마다 그 의문점을 해결해주는 글의 흐름이었습니다. 특히 cache과 stale의 개념에 대해서 비교하여 설명을 해주시고, staleTime, cacheTime이 왜 있어야 하는지에 대해서 잘 이해할 수 있게 정리해주셔서 많은 도움이 되었습니다. staleTime보다 cacheTime을 작게해서 로딩없이 더 빠르게 사용사에게 서비스를 제공할 수 있다는 점은 굉장히 인상 깊었습니다.

axios를 통해서 데이터를 가져오는 방식만을 사용해봐서 기존의 코드들이 react-query를 사용하면 굉장히 간결해지는 점이 아주 좋은 것 같습니다. 그리고 말씀해주신 것처럼 다양한 옵션들이 존재해서 이를 통해 api호출 비용을 줄일 수 있다는 점이 굉장히 매력적으로 다가오네요. 얼른 사용해보고 옵션에 대해서도 공부를 해보고 싶다고 생각이 드는 아티클이었습니다. 좋은 아티클 준비해주셔서 감사합니다.

낙관적 업데이트에 대해서도 알아보니 낙관적 업데이트는 사용자 경험을 개선하고, 서버에서 응답을 받을 때까지 기다리지 않고 사용자에게 빠른 피드백을 제공할 수 있지만 요청이 실패하면 이전 상태로 되돌려야 하므로, 이를 처리하는 롤백 로직을 함께 구현해야 한다고 합니다. react-query에서는 쿼리 객체의 onMutate 옵션을 사용하는 것으로 낙관적 업데이트를 구현할 수 있다고 합니다.

덕분에 리액트 쿼리를 잘 접할 수 있었습니다. 감사합니다

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

react-query가 익숙하지 않은 사람으로서 너무 좋은 글이었습니다. 상태의 정의 부터 이를 사용하는 이유 이점, stale과 캐시의 개념 등 정말 react-query를 처음 접하는 사람이 봤을 때 궁금하고 찾아봐야할 개념들을 잘 정리해주신것 같아요.
평소에 리액트쿼리를 간단하게 써보고 알고는 있었지만 개념에 대해서 생각하고, 실질적인 이점에 대해서 크게 생각해본적이 없었는데 이번글을 통해서 잘 알게 된것 같습니다. 캐싱과 로직의 단순화를 통해서 다양하고 효율적으로 서버상태를 관리 할 수 있을거라는 생각이 들어요. 정말 안사용할 이유가 없는 라이브러리인것 같습니다.
이렇게 딱 핵심을 읽고 주요한 훅인 useQuery와 useMutation까지 딱 읽으니 이거 바로 사용할 수 있을것 같다는 생각이 들어요 ㅎㅎ 덕분에 기본 개념에 대해서 확실하게 알 수 있었습니다.
다음에는 프로젝트 요구사항에 따라서 staleTime과 cacheTime을 조절도 해보고, 더 다양한 활용을 해보고 싶네요. 정말 좋은 아티클 감사합니다..!

답글 달기