모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

21강 / 전체 30강

라우팅 — React Router

6분 읽기 조회 5

TanStack Query(React Query)로 서버 상태를 관리하는 방법, useQuery와 useMutation, 캐싱과 자동 재패칭의 원리를 직접 만든 useFetch와 비교합니다.

🤔 서버 상태 vs 클라이언트 상태

지금까지 배운 useState와 Context는 클라이언트 상태를 관리합니다 — 폼 값, UI 토글, 사용자 선택 등 브라우저에서 만들어지고 브라우저에 사는 데이터입니다. 그런데 실제 앱 데이터 대부분은 서버에서 옵니다. 서버 상태는 성격이 다릅니다:

  • 비동기로 가져와야 합니다
  • 서버의 데이터가 언제든지 바뀔 수 있습니다 (다른 사용자가 수정)
  • 캐싱해서 불필요한 중복 요청을 줄여야 합니다
  • 오래된 데이터를 자동으로 갱신해야 합니다

이 문제를 해결하는 것이 TanStack Query(옛 React Query)입니다.

📦 설치 및 설정

npm install @tanstack/react-query @tanstack/react-query-devtools
// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5,  // 5분간 신선한 데이터로 취급
      retry: 2,                    // 실패 시 2번 재시도
    },
  },
})

ReactDOM.createRoot(document.getElementById('root')!).render(
  <QueryClientProvider client={queryClient}>
    <App />
    <ReactQueryDevtools initialIsOpen={false} />  {/* 개발 도구 */}
  </QueryClientProvider>
)

🔍 useQuery — 데이터 패칭

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

interface Post { id: number; title: string; body: string }

async function fetchPosts(): Promise<Post[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts')
  if (!res.ok) throw new Error('API 요청 실패')
  return res.json()
}

function PostList() {
  const { data: posts, isLoading, error, isRefetching } = useQuery({
    queryKey: ['posts'],       // 캐시 키 — 동일 키면 캐시된 데이터 사용
    queryFn: fetchPosts,       // 데이터를 가져오는 함수
  })

  if (isLoading) return <div>불러오는 중...</div>
  if (error) return <div>오류: {error.message}</div>

  return (
    <>
      {isRefetching && <span className="text-xs text-gray-400">업데이트 중...</span>}
      <ul>
        {posts?.map((post) => (
          <li key={post.id}>
            <h3>{post.title}</h3>
          </li>
        ))}
      </ul>
    </>
  )
}

같은 queryKey를 여러 컴포넌트에서 사용하면 하나의 요청만 발생하고, 캐시된 데이터를 공유합니다. 이것이 직접 만든 useFetch와의 결정적 차이입니다.

✏️ useMutation — 데이터 변경

import { useMutation, useQueryClient } from '@tanstack/react-query'

async function createPost(newPost: Omit<Post, 'id'>): Promise<Post> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    body: JSON.stringify(newPost),
    headers: { 'Content-Type': 'application/json' },
  })
  if (!res.ok) throw new Error('생성 실패')
  return res.json()
}

function NewPostForm() {
  const queryClient = useQueryClient()
  const [title, setTitle] = useState('')

  const mutation = useMutation({
    mutationFn: createPost,
    onSuccess: () => {
      // 성공 시 게시글 목록 캐시 무효화 → 자동 재패치
      queryClient.invalidateQueries({ queryKey: ['posts'] })
      setTitle('')
    },
    onError: (error) => {
      console.error('생성 실패:', error)
    },
  })

  return (
    <form onSubmit={(e) => { e.preventDefault(); mutation.mutate({ title, body: '...', userId: 1 }) }}>
      <input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="제목" />
      <button type="submit" disabled={mutation.isPending}>
        {mutation.isPending ? '저장 중...' : '게시글 추가'}
      </button>
      {mutation.isError && <p className="text-red-500">{mutation.error.message}</p>}
    </form>
  )
}

🔄 직접 만든 useFetch vs TanStack Query

기능useFetch (직접)TanStack Query
캐싱없음자동
중복 요청 방지없음자동 (같은 queryKey)
자동 재패치없음탭 포커스, 재연결 시 자동
재시도없음기본 3회
로딩/에러 상태수동자동
MutationuseEffect 직접 구현useMutation

✅ 정리

  • TanStack Query는 서버 상태 관리 전문 도구 — 캐싱, 재패치, 재시도를 자동으로 처리합니다.
  • useQuery: queryKey + queryFn으로 데이터를 패칭하고 캐싱합니다.
  • useMutation: 생성/수정/삭제 + invalidateQueries로 캐시 갱신.
  • 실무 프로젝트에서는 직접 useFetch를 만드는 것보다 TanStack Query를 사용하세요.

관련 주제

  • react-router-dom 설치
  • Routes Route Link
  • 페이지 이동
  • 동적 경로
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 리액트 입문 — 컴포넌트로 만드는 웹
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

리액트 입문 — 컴포넌트로 만드는 웹

이 강의가 속한 시리즈는 총 30강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...