https://tanstack.com/query/latest/docs/framework/react/overview
Overview | TanStack Query React Docs
TanStack Query (FKA React Query) is often described as the missing data-fetching library for web applications, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your web applications a breeze. Motivation Mo
tanstack.com
커스텀 훅의 문제점과 React Query의 장점
리액트에서 커스텀 훅을 사용하면 동일한 로직을 여러 컴포넌트에서 재사용할 수 있습니다.
하지만, 이 커스텀 훅은 로직의 재사용을 목적으로 만들어진 것이지, 데이터의 재사용이나 상태의 공유를 위한 것이 아닙니다.
이로 인해 발생하는 몇 가지 문제점이 있습니다.
커스텀 훅의 문제점
- 커스텀 훅을 호출할 때마다 새로운 네트워크 요청이 발생하며, 받아온 데이터를 별도로 캐싱하지 않습니다. 즉, 동일한 데이터를 요청하는 컴포넌트들이 있어도 각 컴포넌트는 개별적으로 데이터를 가져오게 됩니다. 이는 불필요한 네트워크 트래픽을 증가시키고, 성능 저하로 이어질 수 있습니다.
- 커스텀 훅은 호출하는 컴포넌트마다 독립적인 상태를 생성합니다. 이로 인해 여러 컴포넌트에서 동일한 상태를 공유하거나 재사용하기 어렵습니다.
- 커스텀 훅에는 네트워크 요청 실패 시 자동으로 재시도하거나, 요청이 진행 중인지, 오류가 발생했는지 등을 손쉽게 관리할 수 있는 기능이 부족합니다. 이러한 기능이 필요하다면 별도로 구현해야 하며, 이는 코드의 복잡성을 증가시킵니다.
import { useState, useEffect } from 'react';
function useFetchData(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('네트워크 응답이 정상적이지 않습니다');
}
return response.json();
})
.then(data => {
setData(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
export default useFetchData;
위의 useFetchData 훅은 네트워크 요청을 수행하고 데이터를 반환하지만, 이 훅을 여러 컴포넌트에서 사용하면 각 컴포넌트마다 개별적으로 네트워크 요청이 발생하게 됩니다.
데이터가 캐싱되지 않기 때문에 동일한 URL로 여러 컴포넌트가 호출될 때도 각각 네트워크 요청이 이루어집니다.
React Query의 장점
React Query는 이러한 커스텀 훅의 문제를 해결해줍니다. 주요 장점으로는
- 데이터 캐싱: 동일한 네트워크 요청에 대해 데이터를 메모리에 캐싱하여, 필요 시 이를 재사용할 수 있습니다. 캐시된 데이터는 설정한 시간 동안 유지되며, 이를 통해 불필요한 네트워크 요청을 줄일 수 있습니다.
- 글로벌 상태 관리: React Query는 글로벌하게 상태를 관리할 수 있어, 여러 컴포넌트에서 동일한 데이터를 공유하고, 상태를 일관되게 유지할 수 있습니다.
- 네트워크 요청 관리: 요청이 실패했을 때 자동으로 재시도하는 기능, 요청의 진행 상태나 오류 발생 여부를 쉽게 확인할 수 있는 기능을 제공합니다. 이를 통해 네트워크 통신의 안정성과 사용자 경험을 크게 향상시킬 수 있습니다.
React Query 기본 사용법
1. React Query 설치
먼저 React Query를 설치해야 합니다. 아래 명령어 중 하나를 사용하여 프로젝트에 React Query를 추가할 수 있습니다.
npm i @tanstack/react-query
# 또는
pnpm add @tanstack/react-query
# 또는
yarn add @tanstack/react-query
# 또는
bun add @tanstack/react-query
2. 기본 설정
React Query를 사용하기 위해서는 QueryClient와 QueryClientProvider를 설정해주어야 합니다.
이를 통해 애플리케이션 전반에 걸쳐 React Query를 사용할 수 있게 됩니다.
import React from "react";
import "./App.css";
import MainProjects from "./components/MainProjects";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<MainProjects />
</QueryClientProvider>
);
}
위 코드에서는 QueryClient를 생성하고, QueryClientProvider로 애플리케이션을 감싸서 React Query를 사용할 준비를 합니다.
3. React Query 사용법
이제 React Query를 사용하여 데이터를 페칭하는 방법을 알아보겠습니다.
useQuery 훅을 사용하여 데이터를 가져오고, 로딩 상태, 에러 상태, 그리고 데이터를 쉽게 관리할 수 있습니다.
기존의 useFetchData 커스텀 훅 대신 React Query를 사용한 버전으로, 데이터를 효율적으로 관리할 수 있습니다.
// MainProducts.tsx
import React from "react";
import Projects from "./Projects";
export default function MainProjects() {
return (
<div>
<h1>프로젝트 목록</h1>
<Projects />
</div>
);
}
// Projects.tsx
import React, { useState } from "react";
import { useQuery } from "@tanstack/react-query";
interface Project {
id: number;
name: string;
isActive: boolean;
}
export default function Projects() {
const [showActiveOnly, setShowActiveOnly] = useState(false);
const { isLoading, error, data: projects } = useQuery({
queryKey: ["projects"],
queryFn: async () => {
console.log("데이터를 가져오는 중...");
const response = await fetch('/data/projects.json');
if (!response.ok) {
throw new Error('네트워크 응답이 정상적이지 않습니다');
}
return response.json();
},
});
const handleChange = () => setShowActiveOnly((prev) => !prev);
if (isLoading) return <p>데이터를 불러오는 중입니다...</p>;
if (error) return <p>에러 발생: {error.message}</p>;
const filteredProjects = showActiveOnly
? projects?.filter((project: Project) => project.isActive)
: projects;
return (
<>
<label>
<input
type="checkbox"
checked={showActiveOnly}
onChange={handleChange}
/>
활성 프로젝트만 보기
</label>
<ul>
{filteredProjects?.map((project: Project) => (
<li key={project.id}>
<article>
<h3>{project.name}</h3>
{project.isActive ? <span>🟢 활성</span> : <span>🔴 비활성</span>}
</article>
</li>
))}
</ul>
</>
);
}
json 데이터
[
{
"id": 1,
"name": "프로젝트 A",
"isActive": true
},
{
"id": 2,
"name": "프로젝트 B",
"isActive": false
},
{
"id": 3,
"name": "프로젝트 C",
"isActive": true
},
{
"id": 4,
"name": "프로젝트 D",
"isActive": false
},
{
"id": 5,
"name": "프로젝트 E",
"isActive": true
}
]
4. React Query의 키 개념
useQuery 훅에서 첫 번째 인자로 queryKey를 전달합니다. 이 키는 고유한 값이어야 하며, 데이터의 캐싱과 관리에 사용됩니다.
두 번째 인자로는 데이터를 페칭하는 함수를 정의합니다. 이 함수는 useQuery가 호출될 때 실행되어 반환된 데이터를 React Query가 내부적으로 관리하게 됩니다.
React Query는 네트워크 요청마다 고유한 키를 할당합니다.
예를 들어, projects라는 키로 데이터를 요청하면 React Query는 이 데이터를 메모리에 캐싱합니다. 이후 다른 컴포넌트에서 동일한 키를 사용하여 데이터를 요청할 때, 이미 캐싱된 데이터가 있다면 네트워크 요청을 새로 하지 않고 캐싱된 데이터를 반환합니다.
이렇게 하면 네트워크 요청을 최소화하고 성능을 최적화할 수 있습니다.
queryKey를 배열로 사용하는 이유
queryKey는 React Query에서 캐싱할 데이터의 고유한 식별자로 사용됩니다.
이 키를 적절히 설정해야 동일한 데이터에 대해 불필요한 네트워크 요청을 줄이고, 캐시된 데이터를 효율적으로 사용할 수 있습니다.
- 단일 키: queryKey를 단일 문자열로 설정할 경우, 해당 키를 사용하는 모든 컴포넌트에서 동일한 캐시 데이터를 공유합니다.
useQuery({ queryKey: ['todos'], ... })
- 복합 키: queryKey를 배열로 설정하면, 더 세밀하게 데이터를 구분할 수 있습니다. 예를 들어, 첫 번째 요소가 동일하더라도 두 번째 요소가 다르면 서로 다른 캐시를 사용하게 됩니다.
useQuery({ queryKey: ['something', 'special'], ... })
이 방식은 다양한 조건별로 데이터를 구분해 캐싱하고자 할 때 매우 유용합니다.
1. 배열형 queryKey의 예시
예를 들어, 동일한 데이터를 다루더라도 상태에 따라 다른 캐시를 사용하고 싶을 때 다음과 같은 방식으로 queryKey를 배열로 설정할 수 있습니다.
// 특정 todo 항목
useQuery({ queryKey: ['todo', 5], ... })
// 특정 todo 항목의 미리보기
useQuery({ queryKey: ['todo', 5, { preview: true }], ...})
// 완료된 todos 목록
useQuery({ queryKey: ['todos', { type: 'done' }], ... })
이렇게 하면 동일한 데이터셋이라도 세부 조건에 따라 다른 캐시를 사용할 수 있습니다.
2. 예시
아래 예시는 queryKey를 배열로 설정하여 활성화된 프로젝트와 전체 프로젝트 데이터를 구분하여 관리하는 방법을 보여줍니다.
캐싱을 적절하게 잘 사용하려면 고유한 키를 잘 사용해야 한다.
import React, { useState } from "react";
import { useQuery } from "@tanstack/react-query";
interface Project {
id: number;
name: string;
isActive: boolean;
}
export default function Projects() {
const [showActiveOnly, setShowActiveOnly] = useState(false);
const { isLoading, error, data: projects } = useQuery({
queryKey: ["projects", showActiveOnly],
queryFn: async () => {
console.log("데이터를 가져오는 중...");
const response = await fetch(`/data/${showActiveOnly ? "active_" : "" }projects.json`);
if (!response.ok) {
throw new Error('네트워크 응답이 정상적이지 않습니다');
}
return response.json();
},
});
const handleChange = () => setShowActiveOnly((prev) => !prev);
if (isLoading) return <p>데이터를 불러오는 중입니다...</p>;
if (error) return <p>에러 발생: {error.message}</p>;
const filteredProjects = showActiveOnly
? projects?.filter((project: Project) => project.isActive)
: projects;
return (
<>
<label>
<input
type="checkbox"
checked={showActiveOnly}
onChange={handleChange}
/>
활성 프로젝트만 보기
</label>
<ul>
{filteredProjects?.map((project: Project) => (
<li key={project.id}>
<article>
<h3>{project.name}</h3>
{project.isActive ? <span>🟢 활성</span> : <span>🔴 비활성</span>}
</article>
</li>
))}
</ul>
</>
);
}
React Query에서 발생할 수 있는 문제점과 해결 방법
React Query는 비동기 데이터 관리를 쉽게 해주는 강력한 도구이지만, 기본 설정으로 사용하면 특정 상황에서 불필요한 네트워크 요청이 반복적으로 발생할 수 있습니다. 이러한 문제는 성능 저하로 이어질 수 있으며, 사용자 경험에도 부정적인 영향을 미칠 수 있습니다.
1. 윈도우 포커스 변경 시 불필요한 네트워크 요청 발생
React Query의 기본 설정에서는 윈도우 포커스가 변경되면, 데이터가 자동으로 다시 페칭(fetching)됩니다. 예를 들어, 사용자가 브라우저의 다른 탭으로 이동했다가 다시 돌아올 때마다 네트워크 요청이 발생할 수 있습니다.
2. 상태 변경 시 반복적인 네트워크 요청
컴포넌트 내에서 특정 상태(예: 활성 프로젝트만 보기 토글)가 변경될 때마다 useQuery 훅이 재실행되어 네트워크 요청이 발생합니다. 이는 사용자가 빈번하게 토글을 사용할 경우, 불필요한 네트워크 트래픽을 유발할 수 있습니다.
3. 컴포넌트 재마운트 시 데이터 재페칭
컴포넌트를 숨겼다가 다시 나타낼 때(즉, 컴포넌트가 재마운트될 때)마다 useQuery가 다시 실행되어 데이터를 다시 페칭합니다. 이는 네트워크 요청의 빈도를 증가시키며, 성능 저하로 이어질 수 있습니다.
해결 방법
import React, { useState } from "react";
import { useQuery } from "@tanstack/react-query";
interface Project {
id: number;
name: string;
isActive: boolean;
}
export default function Projects() {
const [showActiveOnly, setShowActiveOnly] = useState(false);
const { isLoading, error, data: projects } = useQuery<Project[], Error>({
queryKey: ["projects", showActiveOnly],
queryFn: async () => {
console.log("데이터를 가져오는 중...");
const response = await fetch(`/data/${showActiveOnly ? "active_" : ""}projects.json`);
if (!response.ok) {
throw new Error('네트워크 응답이 정상적이지 않습니다');
}
return response.json();
},
staleTime: 60000, // 데이터가 60초 동안 신선하다고 간주됩니다.
gcTime: 1000 * 60 * 10, // gcTime을 사용하여 캐시를 10분 동안 유지합니다.
refetchOnWindowFocus: false, // 윈도우 포커스 시 재페칭 방지
refetchOnMount: false, // 마운트 시 재페칭 방지
refetchOnReconnect: false, // 네트워크 재연결 시 재페칭 방지
});
const handleChange = () => setShowActiveOnly((prev) => !prev);
if (isLoading) return <p>데이터를 불러오는 중입니다...</p>;
if (error) return <p>에러 발생: {error.message}</p>;
const filteredProjects = showActiveOnly
? projects?.filter((project: Project) => project.isActive)
: projects;
return (
<>
<label>
<input
type="checkbox"
checked={showActiveOnly}
onChange={handleChange}
/>
활성 프로젝트만 보기
</label>
<ul>
{filteredProjects?.map((project: Project) => (
<li key={project.id}>
<article>
<h3>{project.name}</h3>
{project.isActive ? <span>🟢 활성</span> : <span>🔴 비활성</span>}
</article>
</li>
))}
</ul>
</>
);
}
- staleTime: 이 옵션은 데이터가 신선하다고 간주되는 기간을 설정합니다. 이 기간 동안에는 네트워크 요청이 발생하지 않으며, 캐시된 데이터가 사용됩니다. 예를 들어, staleTime: 60000은 데이터를 60초 동안 신선하다고 간주합니다.
- gcTime: 캐시된 데이터가 10분 동안 유지되도록 설정합니다.
- refetchOnWindowFocus: 윈도우 포커스가 변경될 때 데이터를 다시 페칭하지 않도록 설정합니다. 기본값은 true이므로, 불필요한 요청을 막기 위해 false로 설정합니다.
- refetchOnMount: 컴포넌트가 마운트될 때마다 데이터를 다시 가져오는 것을 방지합니다. 이 옵션을 false로 설정하면 컴포넌트가 다시 렌더링되더라도 데이터를 다시 페칭하지 않습니다.
- refetchOnReconnect: 네트워크가 끊겼다가 다시 연결될 때 데이터를 자동으로 다시 가져오는 기능을 비활성화합니다. 이 옵션을 false로 설정하면 네트워크가 재연결될 때 불필요한 네트워크 요청을 방지할 수 있습니다.
https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults
Important Defaults | TanStack Query React Docs
Query results by default are structurally shared to detect if data has actually changed and if not, the data reference remains unchanged to better help with value stabilization with regards to useMemo and useCallback. If this concept sounds foreign, then d
tanstack.com
React query Devtools
Devtools는 애플리케이션에서 React Query의 상태를 실시간으로 모니터링하고 디버깅하는 데 매우 유용합니다.
1. React Query Devtools 설치
npm i @tanstack/react-query-devtools
#또는
pnpm add @tanstack/react-query-devtools
#또는
yarn add @tanstack/react-query-devtools
#또는
bun add @tanstack/react-query-devtools
2. Devtools 설정
React Query Devtools를 사용하려면, QueryClientProvider 내부에 ReactQueryDevtools 컴포넌트를 추가해야 합니다.
이 컴포넌트는 Devtools UI를 애플리케이션에 추가하며, 이를 통해 React Query의 상태를 실시간으로 확인할 수 있습니다.
import React from "react";
import "./App.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import MainProjects from "./components/MainProjects";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient();
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<MainProjects />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
'React > 정리' 카테고리의 다른 글
React Query로 페이징된 데이터 로드 구현하기 (1) | 2024.08.31 |
---|---|
useRef로 DOM 요소 참조 및 관리하기 (1) | 2024.08.31 |
React Router(CSR) 활용법 (0) | 2024.08.21 |
리액트 전역 상태 관리: useContext 훅 (0) | 2024.08.12 |
리액트 상태 관리: useReducer, useImmer 알아보기 (0) | 2024.08.12 |