SWR vs React-Query(@tanstack)

given·2024년 9월 2일
post-thumbnail

개요


SWR 그리고 React-Query 모두 데이터를 다루는 데 유용한 라이브러리다. 이 두 라이브러리는 서버 상태 관리의 복잡성을 줄이고, 애플리케이션의 성능을 향상시키며, 사용자 경험을 개선하는 데 중점을 둔다.

프론트엔드 개발을 진행 하면서 서버상태 관리는 필수적인 구현 요소이고 두 라이브러리 모두 사용해봤다.
하지만 둘이 어떻게 다른지 정리해보자.

보통 Next프로젝트에 사용했으니 Next.js(13+) 기준으로 작성해보자.

사용한 이유


API를 통한 서버 상태를 다룰 때 보통 fetch나 인스턴스를 제공해주는 axios를 사용해서 가져온 값을 지역 상태나 전역 상태에 할당해 핸들링 했는데 그럴 필요를 줄이기 위한 방법을 알아보다 서버 상태관리 라이브러리들을 알게되었다. 프론트엔드로써 알아야하는 기술이라 판단되었고 차근차근 프로젝트에 적용해보았다.

공통점(데이터 페칭, 캐싱, 동기화, 업데이트)


두 라이브러리는 사용 방법도 비슷하고 공통점이 많다. 각자 제공하는 hooks로 데이터를 관리할 수 있다.

// swr
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((res) => res.json());

export default function UserProfile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error loading data</div>;

  return (
    <div>
      <h1>User Profile</h1>
      <p>Name: {data.name}</p>
      <p>Email: {data.email}</p>
    </div>
  );
}
//React-Query
import { useQuery } from 'react-query';

const fetcher = async () => {
  const response = await fetch('/api/user');
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  return response.json();
};

export default function UserProfile() {
  const { data, error, isLoading } = useQuery('user', fetcher);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error loading data</div>;

  return (
    <div>
      <h1>User Profile</h1>
      <p>Name: {data.name}</p>
      <p>Email: {data.email}</p>
    </div>
  );
}

이렇게 특정 API를 호출하고 서버 상태를 핸들링 할 수 있다. 각각 swr: useSWR, react-query: useQuery에 두번째 인자에 fetcher 함수를 받아 데이터 페칭할 수 있다. 그리고 둘 모두 캐싱 기능이 있어 동일한 요청 시 네트워크 요청을 줄이고 성능 최적화를 지원한다. 물론 둘 다 캐싱을 끌 수 있다. 두 라이브러리의 주요기능은 서버상태의 캐싱, 실시간 데이터 동기화, 데이터 업데이트이다. 모두 타입스크립트를 지원.
모두 세번째 인자를 옵션값으로 다양한 설정 및 서버 상태를 관리할 수 있다.

차이점


1. 환경

swr은 next.js를 만든 vercel 사에서 제작한 데이터페칭 라이브러리라 next.js 환경에서만 구동될 줄 알았는데 next.js와는 독립적으로 동작한다는 것을 알게됨. useState, useEffect, useContext를 SWR 내부에서 사용하고 있기 때문에 react환경이면 일단 동작은 하는 듯. react-query는 React 뿐만 아니라 Vue나 Svelte 같은 환경에서도 동작하도록 되어있다.
https://tanstack.com/query/latest/docs/framework/react/installation

2. fetcher

각각 라이브러리는 key값을 제공해야하지만 쓰임새는 좀 다르다.
React-query는 단일 string 뿐만 아니라 간단한 단일 string으로 이루어진 배열키 또는 복잡한 객체도 배열키에 담을 수 있었으나 공식문서에서 v3까지는 단일 string만으로도 사용가능했지만 그 이후부터 쿼리키의 최상위는 배열이어야한다고 나와있다. react-query는 기본적으로 쿼리키에 따라 캐싱 관리하며 이는 useEffect에서 의존성 설정과 비슷하다.

swr에서는 키값이 fetcher에 인자로 넘어간다. 문자열, 배열, 객체, 콜백함수 등 다양하게 넘길 수 있고 이는 swrConfig로 fetcher을 설정해 둘 수도 있다. (키값에 함수도 넘어간다는데 이건 처음 알았다. 다음에 해봐야지)

3. Provider

React-query는 Provider가 필요하다.

'use client'

import {
  QueryClient,
  QueryClientProvider,
  isServer,
} from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import * as React from 'react'

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000,
      },
    },
  })
}

let browserQueryClient: QueryClient | undefined = undefined

function getQueryClient() {
  if (isServer) {
    return makeQueryClient()
  } else {
    if (!browserQueryClient) browserQueryClient = makeQueryClient()
    return browserQueryClient
  }
}

export function Providers(props: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
        {props.children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  )
}

허나 swr은 config가 필요한 경우가 아니라면 별도의 provider는 필요없다. 그리고 swr은 swrConfig를 중첩해서 사용할 수 있다.

4. Next

swr은 Next app router 에 요소는 SSG, SSR를 구현 시

 export async function getStaticProps () {
  // `getStaticProps` is executed on the server side.
  const article = await getArticleFromAPI()
  return {
    props: {
      fallback: {
        '/api/article': article
      }
    }
  }
}
 
function Article() {
  // `data` will always be available as it's in `fallback`.
  const { data } = useSWR('/api/article', fetcher)
  return <h1>{data.title}</h1>
}
 
export default function Page({ fallback }) {
  // SWR hooks inside the `SWRConfig` boundary will use those values.
  return (
    <SWRConfig value={{ fallback }}>
      <Article />
    </SWRConfig>
  )
}

위 버전은 Next.js 12 이하 버전이고 그 13 이상 버전은

// app/Article.tsx
'use client';
import useSWR from 'swr'
function Article() {
  const { data } = useSWR('/api/article', fetcher)
  return <h1>{data.title}</h1>
}
// app/page.tsx
export default async function Page() {
  const { data: fallback } = await getServerArticle();
  return (
    <SWRConfig value={{ fallback }}>
      <Article />
    </SWRConfig>
  )
}

이러면 될 듯

서버 컴포넌트에서 복잡한 쿼리 키에 데이터 요청 시 아래와 같이 사용하기를 권장

import { unstable_serialize } from 'swr' // ✅ Available in server components
import { unstable_serialize as infinite_unstable_serialize } from 'swr/infinite' // ✅ Available in server components

두 라이브러리 모두 Next 환경에서 React에서 제공하는 Suspense를 활용한 pre-redering을 구현할 수 있지만 swr에서는 추천하고 있지는 않다.

React-query에서는 provider 감싸고

//app/page.tsx
import React from 'react'
import { HydrationBoundary, dehydrate } from '@tanstack/react-query'
import { pokemonOptions } from '@/app/pokemon'
import { getQueryClient } from '@/app/get-query-client'
import { PokemonInfo } from './pokemon-info'

export default function Home() {
  const queryClient = getQueryClient()
  void queryClient.prefetchQuery(pokemonOptions)
  return (
    <main>
      <h1>Pokemon Info</h1>
      <HydrationBoundary state={dehydrate(queryClient)}>
        <PokemonInfo />
      </HydrationBoundary>
    </main>
  )
}
//app/pokemon-info.tsx
'use client'

import React from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { pokemonOptions } from '@/app/pokemon'

export function PokemonInfo() {
  const { data } = useSuspenseQuery(pokemonOptions)
  return (
    <div>
      <figure>
        <img src={data.sprites.front_shiny} height={200} alt={data.name} />
        <h2>I'm {data.name}</h2>
      </figure>
    </div>
  )
}

// app/pokemon.ts
import { queryOptions } from '@tanstack/react-query'

const pokemonOptions = queryOptions({
  queryKey: ['pokemon'],
  queryFn: async () => {
    const response = await fetch('https://pokeapi.co/api/v2/pokemon/25')
    return response.json()
  },
})

queryClient.prefetchQuery()useSuspenseQuery를 사용해서 Suspense를 이용한 pre-rendering을 사용할 수 있다.

5. 생태계

React-query가 훨씬 많은 솔루션 커뮤니티가 활성화 되어있다.(stackoverflow 등)
또한, 플러그인이나 확장성이 훨씬 많다. 그래서 알아야할게 엄청 많다. devtools도 제공(swr은 없는듯)

6. 공식문서

개인적으론 중요한 건데 vercel은 공식문서가 한국어도 지원한다. 모든 페이지는 아니지만....
하지만 tanstack에서 제공하는 공식 문서가 더 설명이 친절하다. ㅜ

profile
osanThor⚡️블로그 이전했습니다. https://blog.given-log.com

0개의 댓글