웹개발에서 상태관리는 필수적인 부분이다. 특히 클라이언트 상태과 서버 상태를 명확히 이해하고
적절히 활용하는 것은 서비스의 성능과 유지보수성에 직결된다.
이 글에서는 상태관리 정의를 시작으로 클라이언트와 서버 상태 관리의 차이를 알아보고,
서버상태 관리에 사용되는 라이브러리인 SWR, React Query(@tanstack/react-query)를 비교할 예정이다.
상태관리란?
리액트에서 상태(state)란 특정 시점에서 애플리케이션의 데이터가 가지는 값을 의미한다. 상태관리는 이런 데이터를 추적하고 업데이트 하는 과정을 말한다.
일반적으로 리액트 프로젝트에서 사용하는 상태(state)를 세가지로 구분한다.
- Local State: 리액트 컴포넌트 안에서만 사용되는 state
- Global State: Global Store에 정의되어 프로젝트 어디에서나 접근할 수 있는 state
- Server State: 서버로부터 받아오는 state
클라이언트 상태 관리 (Client State)
- 웹 에플리케이션의 프론트엔드에서 관리되는 데이터
- 클라이언트 상태는 크게 Local State, Global State 로 나눌 수 있다.
- 클라이언트 상태는 사용자의 브라우저 또는 애플리케이션 내에 저장되어 관리되고, 서버와 상호작용하지 않는다.
- 단일 사용자 환경에서만 유효하며, 클라이언트 간에 데이터 공유가 이루어지지 않는다.
- 주로 UI 상태, 사용자 입력 폼 데이터, 임시 데이터 등을 저장.
- 클라이언트 상태 관리는 주로 React의 내장 상태관리 도구(useState, useReducer 등)나 전역 상태관리 라이브러리(Zustand, Redux 등)를 사용하여 처리한다.
서버 상태 (Server State)
- 웹 애플리케이션의 프론트엔드와 서버 간의 데이터를 동기화하기 위해 관리되는 상태
- 서버에서 제공되는 데이터를 나타내고 데이터베이스, 파일 시스템, 캐시 등에 저장되어 관리된다.
- 다중 사용자 환경에서 공유되며, 모든 클라이언트에게 동일한 데이터를 제공한다.
- 주로 사용자 정보, 게시물, 상품 정보 등과 같은 영속적인 데이터를 저장한다.
- 서버 상태 관리는 주로 React Query, SWR과 같은 라이브러리를 사용하여 효율적으로 처리된다.
서버 상태 관리가 하는 작업들
- 로딩 상태 관리: 데이터를 가져오는 동안 사용자에게 적절한 피드백 제공.
- 캐싱 및 동기화: 자주 사용되는 데이터를 효율적으로 관리하여 네트워크 요청을 줄임.
- 에러 처리 및 재시도: 네트워크 문제나 서버 오류에 대한 대처와 재시도 로직 구현.
- Stale-While-Revalidate 전략: 최신 데이터를 빠르게 제공하면서 백그라운드에서 데이터를 동기화하여 사용 경험 개선.
- 데이터 변조 방지: 서버에서 가져온 데이터를 신뢰할 수 있도록 검증.
서버 상태 관리 라이브러리:
React Query와 SWR
- SWR
Vercel 에서 개발한 서버 상태 관리 라이브러리로, Stale-While-Revalidate
전략을 기반으로 설계되었다.Stale-While-Revalidate
전략이라 함은 먼저 캐시(stale)로부터 데이터를 반환한 후, fetch 요청(revalidate)을 하고, 최종적으로 최신화된 데이터를 가져오는 전략이다. 설정이 간단하고 경량화되어 있어 상대적으로 작은 프로젝트에서 효과적이다.
주요 특징
- 데이터 캐싱 및 중복 요청 방지: 동일한 데이터를 요청할 경우 자동으로 캐싱하여 네트워크 트래픽을 감소
- 자동 재검증: 데이터를 백그라운드에서 자동으로 최신 상태로 유지
- 유연한 API: React Hook 형태로 제공되어 간결하고 직관적인 코드를 작성할 수 있다
- 경량성: 작은 번들 크기로 간단한 프로젝트에 적합
- React Query
서버로부터 데이터 가져오기, 데이터 캐싱, 캐시 제어 등 데이터를 쉽고 효율적으로 관리할 수 있는 라이브러리다. 복잡한 데이터 요구 사항을 가진 애플리케이션에서 유용하며, 대규모 프로젝트에서 많이 사용된다.
(React Query라는 이름으로 시작했지만, v4부터 Vue나 Svelte 등의 다른 프레임워크에서도 활용할 수 있도록 기능이 확장되며 TanStack Query라는 이름으로 변경됨)
주요 특징
- 데이터 페칭, 캐싱, 동기화: 데이터를 효율적으로 관리하여 네트워크 요청을 최소화
- 로딩 및 에러 상태 관리: 데이터를 가져오는 동안의 상태를 자동으로 처리
- 쿼리 무효화 및 갱신: 특정 조건에서 데이터를 새로 고칠 수 있는 기능을 제공
- 폴링 및 실시간 업데이트: 주기적으로 데이터를 가져오거나 실시간 동기화를 지원
React Query와 SWR 차이
👀 Provider 사용
- SWR: 별도의 Provider 없이 컴포넌트에서 바로 사용할 수 있다.
- React Query: 반드시
QueryClientProvider
로 컴포넌트를 감싸야 한다. 이 구조는 애플리케이션 전체에 걸친 데이터 관리를 일관되게 해주며, 쿼리 상태를 더 잘 통합할 수 있게 돕는다.
👀 다중 API 지원
- SWR: 단일 API 엔드포인트에서 데이터를 가져오는 것으로 제한된다. 단순한 API호출이나 데이터가 많이 복잡하지 않은 상황에서 효율적.
- React Query: 여러 API 엔드포인트에서 데이터를 가져올 수 있어, 복잡한 애플리케이션이나 여러 데이터 소스가 필요한 경우 유연하게 대응이 가능하다. 대규모 데이터를 다루는 상황에서 효율적.
👀 데이터 캐싱 및 갱신
- SWR :
Stale-While-Revalidate
전략을 기본으로 사용하여, 오래된 데이터를 반환하면서 백그라운드에서 새로운 데이터를 가져온다. 즉, 종속성이 변경되면 자동으로 데이터를 다시 가져온다.
- 첫 번째 요청이 완료될 때까지 초기화되지 않은 값을 반환한다.
- 자동으로 데이터를 캐싱하여 네트워크 요청을 줄일 수 있지만, 오래된 데이터(stale data)를 관리하는 방법이 부족하다. 데이터가 자주 업데이트되지 않는 환경에서 적합하다.
- React Query:
- 데이터 갱신을 위해 쿼리를 명시적으로 무효화(invalidateQueries)하거나 다시 호출해야된다. 더 세밀한 제어가 가능하며, 캐싱 및 동기화 전략을 자유롭게 설정할 수 있다.
- 캐시된 값을 즉시 반환한다 (존재하는 경우)
- 캐싱에 대한 세밀한 제어가 가능하며, 사용되지 않는 쿼리를 자동으로 Garbage Collection할 수 있다. 데이터가 빈번하게 업데이트되는 환경에서 React Query가 더 유리하다.
👀 데이터 변경처리
- SWR: mutate 함수를 사용해 캐시 데이터를 즉시 업데이트할 수 있다. 서버 요청 없이 UI를 빠르게 갱신할 수 있어 사용자 경험이 중요한 상황에 적합.
- React Query: useMutation 훅을 통해 서버와의 직접적인 상호작용 및 상태 변경을 처리. 데이터의 일관성을 유지하고, 서버와 클라이언트 간 동기화를 보장한다. 이 방식은 서버 상태를 동기화하고 관리하는 데 중점을 두기 때문에, 서버의 데이터를 변경하는 작업이 더 명확하게 처리된다.
👀 실시간 데이터 처리
- SWR: 기본적으로 실시간 기능을 제공하지 않으며, 폴링이나 웹소켓은 직접 구현해야 됨.
- React Query: 폴링 간격 설정을 지원하며, 일정 시간 간격으로 데이터를 최신 상태로 유지해야될 때 유용
👀 렌더링 최적화
- SWR: 쿼리마다 개별적으로 컴포넌트를 업데이트하기 때문에 쿼리 개수가 많으면 렌더링 성능이 떨어질 수 있다.
- React Query: 여러 컴포넌트가 동일한 쿼리를 사용할 경우, 한 번에 묶어서 업데이트하여 성능이 더 뛰어나다. 이를 통해 리렌더링을 줄이고 성능 최적화를 달성할 수 있다.
👀 에러 핸들링
- SWR: 오류 처리 시스템이 없음. 사용자가 직접 오류 처리를 구현해야 한다.
- React Query: 내장된 에러 핸들링 시스템이 있어, 애플리케이션에서 발생하는 오류를 쉽게 관리할 수 있다.
👀 번들 크기
-
SWR: 4.5kB. 경량 라이브러리로 번들 크기가 작고, 단순한 프로젝트에서 적합하다
-
React Query: 15.1kB. 기능이 풍부한 만큼 번들 크기가 상대적으로 크며, 복잡한 프로젝트에서 더 적합하다
👀 개발자 도구
BundlePhobia 측정 결과
어떤 라이브러리를 선택해야 할까?
둘의 가장 큰 차이점은 데이터 처리 방식이 아닐까 싶다.
React Query는 데이터를 갱신하려면 명시적으로 쿼리를 무효화하거나 재호출해야 한다.
SWR은 오래된 데이터를 반환한 후 백그라운드에서 최신 데이터를 가져온다. UI를 먼저 업데이트하고 필요시 서버와 동기화하는 방식이다.
이 차이는 사용자 경험과 애플리케이션 설계에 직접적인 영향을 미친다.
React Query의 방식은 데이터의 일관성을 유지하는 데 유리하다. 예를 들어, 데이터를 변경할 때 항상 서버와 동기화되므로, 클라이언트와 서버의 데이터가 불일치할 가능성이 줄어든다. 하지만 이를 위해 쿼리 무효화와 같은 명시적인 작업이 필요하며, 상대적으로 코드의 복잡도가 증가할 수 있다.
반면 SWR의 방식은 빠른 사용자 피드백을 제공하는 데 중점을 둔다. UI가 즉각적으로 반응하며, 사용자는 최신 데이터를 기다리는 동안에도 자연스러운 경험을 할 수 있다. 하지만 서버와 클라이언트 데이터 간의 불일치가 발생할 가능성이 있고, 이러한 부분은 추가 로직으로 보완해야 할 수 있다.
그렇다면 어떤 상황에서 어떤 방식을 선택해야 할까?
프로젝트의 목적과 우선순위를 기준으로 적절한 도구를 선택해야 한다.
React Query는 데이터의 정확성과 동기화가 중요한 대규모 애플리케이션에서 적합하다. 예를 들어, 금융 서비스, 대시보드, 또는 다중 사용자 환경에서 서버 상태가 중요한 경우 React Query의 명시적 데이터 처리 방식이 유리하다.
SWR은 사용자 중심의 빠른 반응성이 중요한 소규모 프로젝트나 단일 사용자 중심 애플리케이션에 적합하다. 예를 들어, 개인 블로그, 게시판, 또는 단순한 데이터 조회 서비스에서는 SWR의 전략이 더 적합하다.
데이터의 정확성과 일관성이 중요한지, 아니면 사용자 경험과 빠른 반응성이 더 중요한지에 따라 선택이 달라질 것이다.
npm trends 를 확인하면 @tanstack/react-query (이 글에서는 react query라고 칭함)의 다운로드 수가 확연히 높긴 하다.
참고
https://swr.vercel.app/ko
https://fe-developers.kakaoent.com/2022/220224-data-fetching-libs/
https://voyage-dev.tistory.com/159
https://velog.io/@okko8522/%EC%84%9C%EB%B2%84-%EC%83%81%ED%83%9C-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%83%81%ED%83%9C