열정 실천

[React] 서버 데이터 관리 :: React-Query 사용하기 본문

개발 공부/React

[React] 서버 데이터 관리 :: React-Query 사용하기

구운오니 2025. 1. 3. 22:47
728x90

클라이언트 상태(Client State)란?

  • 서버와 독립적인 데이터로, UI 동작이나 사용자 입력에 의해 생성 및 관리다.
  • 컴포넌트 간 데이터 공유, 사용자 인터페이스 상태(모달 열림/닫힘, 폼 입력 값 등)에 주로 사용된다.
  • React 내장 기능으로는 { useState, useReducer, useContext } 등이 있다. 
  • 클라이언트 상태 관리 라이브러리는 Redux, Zustand, Recoil 등이 있다. 

서버 상태(Server State)란?

  • 서버에서 제공된 데이터로, 클라이언트에서 이를 받아 관리한다. 
  • API를 통해 서버 데이터를 가져오고, 캐싱, 동기화, 에러 처리 등의 작업이 필요하다.
  • React 내장 기능으로는 { useEttect, fetch, axios } 등이 있다. 
  • 서버 상태 관리 라이브러리는 React Query, SWR, Redux Toolkit Query 등이 있다. 
  React 내장 기능  라이브러리 
클라이언트 상태 useState, useReducer, Context Redux, Zustand, Recoil
서버 상태 useEffect, fetch, axios React Query, SWR, Redux Toolkit Query

 

 

오늘 새로 공부할 React-Query는 서버 상태 관리의 대표 라이브러리이다. 

 

 

 

 

🤔 그래서 React-Query를 왜 쓰는건데?

▶️▶️ 자동 캐싱, 리페칭, 로딩 및 에러 상태 관리 제공한다. 

 

React-Query를 쓰는 이유 1.  캐싱 

캐싱(Caching)은 React Query의 핵심 기능 중 하나로, 데이터를 서버에서 가져온 후 로컬에 저장해놓고, 이를 중복 요청 없이 재사용할 수 있게 하는 메커니즘이다. 이러한 캐싱은 애플리케이션의 성능과 효율을 크게 향상시킨다. 

 

▪️쿼리 키를 사용한 캐시 식별 및 저장

const fetchUser = () => axios.get('/api/user');

const { data } = useQuery(['user'], fetchUser); // 'user'가 캐싱 키

 

 

위의 코드는 'user'라는 쿼리 키를 기준으로 캐시를 생성하고, 같은 키로 데이터를 재사용한다. 

 

만약 동일한 키를 사용하는 요청을 하면 서버 요청 없이 캐싱된 데이터를 반환한다. 

 

 

▪️데이터 무효화 

 

캐싱된 데이터가 오래되었거나 변경되었을 가능성이 있는 경우 데이터를 다시 가져와햐하는데 

이를 데이터 무효화라고 하며 invalidateQueries를 통해 수동으로 무효화 할 수 있다. 

const queryClient = useQueryClient();

const handleUpdateUser = async () => {
  await axios.post('/api/update-user', { name: 'John Doe' });
  queryClient.invalidateQueries(['user']); // 'user' 키의 캐시를 무효화
};

 

 

▪️캐싱 만료 시간 관리

 

React Query는 데이터를 stale(오래된) 상태와 fresh(신선한) 상태로 구분한다.

기본적으로 데이터는 가져온 직후 fresh 상태이며, 이후 staleTime(기본값: 0ms)이 지나면 stale 상태가 된다.

stale 상태일 때는 캐싱된 데이터를 사용하지만, 필요 시 백그라운드에서 데이터를 새로 가져온다.

 

 

 

const { data } = useQuery(['user'], fetchUser, {
  staleTime: 60000, // 60초 동안 데이터를 fresh 상태로 유지
});

 

 

▪️캐싱시간 TTL 

 

TTL(cacheTime)은 캐싱된 데이터가 메모리에 유지되는 시간을 결정한다. 기본값은 5분으로, 이 시간이 지나면 캐시가 삭제된다.

 

 

const { data } = useQuery(['user'], fetchUser, {
  cacheTime: 1000 * 60 * 10, // 10분 동안 캐시 유지
});

 

 

 

 

React-Query를 쓰는 이유 2. 리페칭

리페칭(Refetching)이란 이미 가져온 데이터를 다시 가져오는 작업을 의미한다.

서버에서 데이터가 변경이 되었거나, 네트워크가 끊겼다가 다시 연결되었거나, 사용자가 새로고침을 했거나 등

데이터의 정확성과 최신성을 유지하기 위해 서버에서 데이터를 다시 요청할 필요가 있다. 

 

자동 리페칭

const { data, isFetching } = useQuery(['posts'], fetchPosts, {
  refetchOnWindowFocus: true,   // 브라우저 포커싱 시 리페칭
  refetchOnReconnect: true,    // 네트워크 복구 시 리페칭
  refetchInterval: 60000,      // 60초마다 데이터 새로고침
});

 

수동 리페칭

const { data, refetch, isFetching } = useQuery(['user'], fetchUser);

return (
  <div>
    <button onClick={() => refetch()}>Refresh</button>
    {isFetching ? <p>Fetching data...</p> : <p>User: {data.name}</p>}
  </div>
);

 

이렇게 수동, 자동 두 가지 방법으로 리페칭이 호출되면

React- Query는 기존 데이터를 캐싱하고, 새 데이터를 요청한 후 UI를 업데이트한다. 

 

 

 

React-Query를 쓰는 이유  3.  로딩 및 에러 상태 관리 

데이터 페칭 과정에서 발생하는 로딩 상태에러 상태를 간단하게 관리할 수 있게 하여

별도의 로직 없이 데이터의 요청 상태를 UI와 동기화하기 쉽다. 

로딩 상태 관련 반환값 isLoading  쿼리가 처음 실행되고 데이터를 가져오는 동안 true  * 첫 번째 로드에만 적용
isFetching  데이터를 가져오는 동안 항상 true / 리페칭 중에도 true
에러 상태 관련 반환값  isError 데이터 페칭 중 에러가 발생한 경우 true
error 발생한 에러 객체를 반환

 

이러한 상태 반환값은 useQuery를 사용할 때 데이터를 가져오는 것처럼 받아올 수 있다. 

const { data, isError } = useQuery(['user'], fetchUser, {
  retry: 3, // 3번까지 재시도
  retryDelay: 2000, // 재시도 간격 2초
});

 

 

 

이외에도 useQuery가 반환하는 데이터는 여러가지이다. 

React Query의 useQuery가 반환하는 데이터의 종류

{
  data,           // 서버에서 가져온 데이터
  error,          // 에러 객체
  isLoading,      // 로딩 중 여부
  isFetching,     // 백그라운드 리페칭 여부
  isError,        // 에러 발생 여부
  isSuccess,      // 성공 여부
  status,         // 현재 상태 ('idle', 'loading', 'error', 'success')
  isStale,        // 데이터가 오래되었는지 여부
  dataUpdatedAt,  // 데이터 마지막 업데이트 시간
  errorUpdatedAt, // 에러 마지막 발생 시간
  refetch,        // 데이터를 다시 가져오는 함수
  remove,         // 캐싱된 데이터 제거 함수
  fetchStatus,    // 현재 페칭 상태 ('fetching', 'paused', 'idle')
  isPaused,       // 쿼리가 일시 중지되었는지 여부
}

 

 

 

 

👇 기존 코드에  React-Query를 적용해보자!! 👇

 

 

1. 기존 코드 (useState, useEffect, Axios 사용)

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const MyPage = () => {

 const [babyName, setBabyName] = useState('아기이름'); //아기 이름
 const [birthDate, setBirthDate] = useState('2000-01-01'); //아기 생일
 
 useEffect(() => {
	const fetchBabyData = async () => {
		try {
			const { data: { baby } } = await axios.get('/users/me'); //서버에서 데이터 가져오기
            
			setBabyName(baby?.babyName || '아기이름');
			setBirthDate(baby?.birthDate || '2000-01-01');
		} catch (error) {
			console.error('Error fetching baby data:', error);
		}
	};
	fetchBabyData();
 }, []);
  
  
 return (
	<div>
		<h1>아기 정보</h1>
		<p>이름: {babyName}</p>
		<p>생일: {birthDate}</p>
	</div>	
 )
 
}

 

지금까지는 useState를 사용하여 가져올 데이터 변수를 선언한 뒤, axios로 데이터를 불러와 setState 호출을 통해 응답 당시의 server state를  wrapping 하였다.

 

 

1. 정한 코드 (Axios, React-Query 사용)

import React from 'react';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

// 서버 데이터 가져오는 함수
const fetchBabyData = async () => {
const { data } = await axios.get('/users/me');
  return data.baby;
};

const MyPage = () => {
  // React Query를 사용해 데이터 페칭
  const { data: baby, isLoading, error } = useQuery(['babyData'], fetchBabyData, {
    initialData: {
      babyName: '아기이름',
      birthDate: '2000-01-01',
    }, // 초기값 설정
  });

  // 로딩 상태 처리
  if (isLoading) return <div>Loading...</div>;

  // 에러 처리
  if (error instanceof Error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>아기 정보</h1>
      <p>이름: {baby.babyName}</p>
      <p>생일: {baby.birthDate}</p>
    </div>
  );
};

export default MyPage;

 

수정한 코드는 axios를 통해 데이터를 가져오는 함수를 이용하여 useQuery는 'babyData'라는 쿼리키로 데이터를 가져온다. 

데이터가 로드 중(isLoading)이면 "Loading..." 메시지를 표시하고 서버 요청 중 오류(error)가 발생하면 에러 메시지를 표시한다. 

 

 

 

 

 

 

 

 

출처 : https://tech.kakaopay.com/

 

 

728x90