※ 체크리스트
① React Query의 개념과 필요성
② React Query의 주요 기능(데이터 패칭, 캐싱, 뮤테이션) 익히기
③ React Query를 활용한 서버 상태 관리 방법의 실습
1. React Query
1-1. React Query의 정의
React 애플리케이션에서 서버 상태를 관리하고 데이터 패칭과 캐싱을 간단하게 만들어주는 라이브러리
☞ 서버 상태(server state) 관리에 특화된 라이브러리
1-2. React Query 주요 특징
① 데이터 동기화 : React Query가 클라이언트 상태와 서버 상태를 동기화하는 능력
● React Query는 자동으로 데이터의 **신선도(freshness)**를 추적하고 필요에 따라 백그라운드에서 데이터를 다시 가져와 UI를 업데이트한다
● 이를 통해 사용자가 보고 있는 데이터가 항상 최신 상태인지 확인할 수 있다
● ex) 사용자가 페이지를 다시 방문하거나 창을 포커스할 때, React Query는 데이터가 오래되었는지 검사하고 최신 상태로 동기화한다
② 자동 캐싱 : 데이터가 변경되지 않으면 캐시된 데이터를 사용하여 네트워크 요청 감소
● React Query가 API 호출 결과를 메모리 내에서 관리하여, 동일한 요청이 있는 경우 캐시된 데이터를 제공하는 기능
● 캐시된 데이터는 일정 시간 동안 유지되며, 캐시 지속 시간(stale time)이 지나면 데이터를 리패치하여 업데이트할 수 있다 → 네트워크 요청 횟수를 줄이고 애플리케이션 성능을 최적화할 수 있다
● ex) useQuery 훅을 사용하면 데이터를 가져올 때 자동으로 캐시된다
③ 리트라이(Retry)와 리패치(Refetch) : 실패한 요청을 자동으로 재시도하거나 데이터가 오래된 경우 자동으로 새로고침
● 리트라이(Retry) : React Query는 API 요청이 실패할 경우 자동으로 요청을 다시 시도할 수 있는 기능을 재공
기본적으로 실패 시 세번까지 재시도하며, 설정에 따라 조정할 수 있다
● 리패치(Refetch) : 일정 조건이 충족되는 경우 React Query는 데이터를 다시 가져온다
사용자가 페이지를 다시 불러오거나, 네트워크 상태가 변경되었을 때 등 데이터를 리패치할 수 있다
● ☞ 리트라이와 리패치는 앱의 신뢰성과 유연성을 높여, 네트워크 오류가 있을 때도 안정적인 데이터 제공을 보장한다
④ 개발자 도구 : 상태 디버깅을 위한 React Query Devtools 제공
브라우저 확장 또는 React 애플리케이션 내 사용할 수 있는 도구
애플리케이션의 쿼리 상태, 캐시 데이터, 리패치 시점 등을 실시간으로 모니터링하고 디버깅할 수 있는 인터페이스를 제공한다
1-3. 구성 요소
▶ Query : 데이터를 가져오는 작업
▶ Mutation : 데이터를 생성, 수정, 삭제하는 작업
▶ Query Client : React Query의 중앙 관리 객체
1-4. 동작 흐름
1) 데이터 가져오기 (Query)
● useQuery 훅을 사용해 데이터를 가져온다
● 자동으로 캐시되며 동일한 키를 가진 요청은 캐시를 재활용한다
import { useQuery } from '@tanstack/react-query';
const fetchPosts = async () => {
const res = await fetch('/api/posts');
return res.json();
};
const Post \ () => {
const { data, isLoading, error } = useQuery(['posts'], fetchPosts);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
};
2) 데이터 갱신 (Mutation)
● useMutation 훅을 사용해 데이터를 업데이트하거나 삭제할 수 있습니다
● 성공적으로 요청을 처리하면 자동으로 해당 데이터와 연관된 쿼리가 갱신됩니다
import { useMutation, useQueryClient } from '@tanstack/react-query';
const deletePost = async (postId) => {
await fetch('/api/posts/${postId}', { method: 'DELETE' });
};
const PostList = () => {
const queryClient = useQueryClient();
const mutation = useMutation(deletePost, {
onSuccess: () => {
queryClient.invalidateQueries(['posts']); //'posts'쿼리 갱신
},
});
const handleDelete = (id) => mutation.mutate(id);
// ...
};
2. React Query 설치 및 기본 설정
React Query 설치
npm install @tanstack/react-query
Query Client 설정
애플리케이션의 최상위 컴포넌트에 Query Client를 설정한다
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
const queryClient = new QueryClient();
function Root () {
return (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
}
export default Root;
3. React Query 주요 기능 및 구현
3-1. 프로젝트 생성
npx create-react-app react-query-demo
3-2. React Query 설치
npm install @tanstack/react-query
3-3. React Query 개발자 도구 설치 (필수 아님)
npm install @tanstack/react-query-devtools
3-4. 구현
1단계 : 데이터 패칭
▶ 서버에서 데이터를 가져오는 기본 예제
> Posts.js 생성
JSONPlaceholder API를 사용해 posts 데이터를 패칭한다
// src/Posts.js
import React from 'react';
import { useQuery } from '@tanstack/react-query';
function fetchPosts() {
return fetch('https://jsonplaceholder.typicode.com/posts').then((res) =>
res.json()
);
}
function Posts() {
// 기존
// const { data, isLoading, isError } = useQuery(['posts'], fetchPosts);
// Uncaught Error: Bad argument type. Starting with v5, only the "Object" form is allowed when calling query related functions.
// React Query v5에서는 모든 쿼리 관련 함수(useQuery, useMutation, useInfiniteQuery 등)가 객체 형태로 인자를 전달해야 합니다
const { data, isLoading, isError } = useQuery({
queryKey: ['posts'], // 쿼리 키
queryFn: fetchPosts, // 데이터 패칭 함수
staleTime: 5000, // 캐싱된 데이터가 신선한 상태로 유지되는 시간(ms)
});
if(isLoading) return <p>Loading...</p>;
if(isError) return <p>Error fetching data.</p>;
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default Posts;
① fetchPosts 함수는 JSONPlaceholder API에서 게시글 데이터를 가져온다
② useQuery 훅을 통해 fetchPosts 호출을 관리하며 데이터(data), 로딩상태(isLoading), 오류 상태(isError)를 다룬다
③ data는 fetchPosts 함수에서 반환된 결과값(게시글 배열)을 저장한다
④ 화면에서는 data 배열의 각 항목에서 post.title 값을 <li>태그 안에 렌더링한다
⑤ useQuery와 useMutation이 React Query v5부터 객체 형태로만 인자를 받도록 변경되었다
2단계 : 데이터 뮤테이션(Mutation)
▶ 데이터를 생성, 수정, 삭제하는 작업
> CreatePost.js 생성
import { useMutation } from '@tanstack/react-query';
function createPost(newPost) {
return fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newPost),
}).then((res) => res.json());
}
function CreatePost() {
// const mutation = useMutation(createPost);
const mutation = useMutation({
mutationFn: createPost, // 데이터 생성 함수
});
const handleCreate = () => {
mutation.mutate({ title: 'New Post', body: 'This is a new post' });
};
return (
<div>
<button onClick={handleCreate}>Create Post</button>
{mutation.isLoading && <p>Creating post...</p>}
{mutation.isSuccess && <p>Post created!</p>}
</div>
);
}
export default CreatePost;
① createPost 함수는 JSONPlaceholder API에 POST 요청을 보내 새로운 게시글을 생성한다
② useMutation 훅을 사용해 createPost 함수 호출을 관리하며, 생성중(isLoading)과 성공 상태(isSuccess)를 확인할 수 있다
③ 버튼 클릭 시 mutation.mutate가 호출되어 새로운 게시글이 만들어진다
3단계 : 캐싱 및 리패치
▶캐싱된 데이터를 사용하는 경우
> Post.js 파일의 staleTime을 추가하는 옵션을 넣어준다
const { data } = useQuery(['posts'], fetchPosts, {
staleTime: 5000, // 데이터가 5초 동안 신선한 상태로 유지
});
staleTime : React Query가 데이터를 "신선한 상태(fresh)"로 간주하는 기간을 정의한다
● staleTime:500은 데이터가 로드된 후 5초 동안은 백그라운드에서 리패치(refetching)를 하지 않는다는 의미
● 5초가 지나면 데이터를 오래된 상태(stale)로 간주하고 필요 시 다시 서버에서 데이터를 가져온다
☞ 페이지를 다시 방문하거나 사용자가 새로고침 하지 않아도 데이터를 효율적으로 관리할 수 있다
3-5. React Query Devtool 사용 및 실행
넣어줄 코드
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
function Root() {
return (
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initiallsOpen={false} />
</QueryClientProvider>
);
}
> App.js 수정
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; // Devtools 임포트
import Posts from './Post';
import CreatePost from './CreatePost';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div>
<h1>React Query 실습</h1>
<Post />
<CreatePost />
</div>
<ReactQueryDevtools initialIsOpen={false} /> {/* Devtools 추가 */}
</QueryClientProvider>
);
}
export default App;
① QueryClient와 QueryClientProvider는 React Query의 전역 상태 관리 및 캐싱을 활성화한다
② <Post />는 게시글 목록을 보여주고, <CreatePost />는 새 게시글 생성 버튼과 상태 메시지를 관리한다
③ React Query DevTools를 포함해 쿼리를 시각적으로 디버깅할 수 있다
3-6. 실행
npm start
4. 실습 과제
① 서버에서 데이터를 패칭하여 화면에 표시하세요
② 데이터를 생성하거나 수정하는 버튼을 구현하세요
③ React Query Devtools를 사용해 상태를 확인하세요
상단에 게시글 목록 표시
하단에 게시글 제목과 본문을 입력
☞ JSONPlaceholder의 API 사용해 데이터 생성/수정을 처리한다
4-1. 설정
1) 프로젝트 생성
npx create-react-app react-query-practice
2) React Query 설치
npm install @tanstack/react-query
3) React Query 개발자 도구 설치
npm install @tanstack/react-query-devtools
4) 프로젝트 구조
src/
App.js
Posts.js
CreateUpdatePost.js
4-2. 구현
1) App.js
> App.js 수정
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import Posts from './Posts';
import CreateUpdatePost from './CreateUpdatePost';
// React Query Client 생성
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<h1>React Query 과제</h1>
<h2>1. 서버에서 데이터를 패칭하여 화면에 표시</h2>
<Posts />
<h2>2. 데이터를 생성하거나 수정하는 버튼</h2>
<CreateUpdatePost />
<ReactQueryDevtools initialIsOpen={false} />
</div>
</QueryClientProvider>
)
}
export default App;
③ ReactQueryDevtools를 추가해 React Query의 상태를 시각적으로 확인 가능
2) Post.js
> Post.js 생성
서버에서 데이터를 가져와 화면에 게시한다
import React from 'react';
import { useQuery } from '@tanstack/react-query';
function fetchPosts() {
return fetch('https://jsonplaceholder.typicode.com/posts').then((res) =>
res.json()
);
}
function Posts() {
const { data, isLoading, isError } = useQuery({
queryKey: ['posts'], // 쿼리 키
queryFn: fetchPosts, // 서버 데이터 패칭 함수
});
if (isLoading) return <p>Loading posts...</p>;
if (isError) return <p>Error fetching posts</p>;
return (
<table
style={{
width: '100%',
borderCollapse: 'collapse',
marginTop: '20px',
textAlign: 'left',
}}
>
<thead>
<tr style={{ backgroundColor: '#f4f4f4' }}>
<th style={{ border: '1px solid #ddd', padding: '8px' }}>ID</th>
<th style={{ border: '1px solid #ddd', padding: '8px' }}>Title</th>
<th style={{ border: '1px solid #ddd', padding: '8px'}}>Body</th>
</tr>
</thead>
<tbody>
{data.slice(0, 10).map((post) => (
<tr key={post.id}>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{post.id}</td>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{post.title}</td>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{post.body}</td>
</tr>
))}
</tbody>
</table>
);
}
export default Posts;
① 서버에서 데이터를 패칭하여 화면에 표시
● Post.js에서 useQuery 훅을 사용해 JSONPlaceholder API에서 데이터를 패칭
● 게시글 목록을 테이블 형식으로 렌더링
3) CreateUpdatePost.js
> 생성
데이터를 생성하는 버튼, 수정하는 버튼 구현
import React, { useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
// 새 게시글 생성 함수
function createPost(newPost) {
return fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
}).then((res) => res.json());
}
// 게시글 수정 함수
function updatePost(updatedPost) {
return fetch('https://jsonplaceholder.typicode.com/posts/${updatedPost.id}', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedPost),
}).then((res) => res.json());
}
function CreateUpdatePost() {
const [postId, setPostId] = useState('');
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const queryClient = useQueryClient();
// 생성/수정 Mutation
const createMutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
queryClient.invalidateQueries(['posts']); // 게시글 목록 새로고침
},
});
const updateMutation = useMutation({
mutationFn: updatePost,
onSuccess: () => {
queryClient.invalidateQueries(['posts']);
},
});
const handleCreate = () => {
createMutation.mutate({ title, body });
};
const handleUpdate = () => {
updateMutation.mutate({ id: Number(postId), title, body });
};
return(
<div>
<input
type="text"
placeholder="Post ID (for update)"
value={postId}
onchange={(e) => setPostId(e.target.value)}
style={{ marginRight: '10px' }}
/>
<input
type="text"
placeholder="Title"
value={title}
onChenge={(e) => setTitle(e.target.value)}
style={{ marginRight: '10px' }}
/>
<input
type="text"
placeholder="Body"
value={body}
onChange={(e) => setBody(e.target.value)}
style={{ marginRight: '10px' }}
/>
<button onClick={handleCreate} style={{ marginRight: '10px' }}>
Create Post
</button>
<button onClick={handleUpdate}>Update Post</button>
<div style={{ marginTop: '10px' }}>
{createMutation.isLoading && <p>Creating post...</p>}
{createMutation.isSuccess && <p>Post created successfully!</p>}
{updateMutation.isLoading && <p>Updating post...</p>}
{updateMutation.isSuccess && <p>Post updated successfully!</p>}
</div>
</div>
);
}
export default CreateUpdatePost;
② 데이터를 생성하거나 수정하는 버튼 구현
● 두 개의 Mutation을 사용
createPost : 새 게시글을 생성 / updatePost : 기존 게시글을 수정
● 각각의 버튼으로 데이터 조작 가능
● 성공 시 queryClient.invalidateQueries를 호출해 게시글 목록 갱신
☞ 이 코드는 JSONPlaceholder의 샘플 API를 사용하므로, 데이터 생성/수정이 실제로 저장되지는 않지만 요청 응답은 성공적으로 처리된다
4) 실행
npm start
데이터 생성 버튼 실행해보기 → 글 작성
데이터 수정 버튼 실행해보기
※ 정리
Q&A
① React Query와 Redux의 차이점은 무엇일까?
→ 둘 다 상태관리를 위한 도구지만 사용목적과 상태 관리 방식에 차이가 있다
→ React Query는 서버 상태 관리에 초점이 맞춰져 있어 API 기반 데이터 패칭 및 캐싱을 효율적으로 처리하는 경우에 적합하고, Redux는 클라이언트에서 애플리케이션의 상태를 한 곳에서 중앙 집중적으로 관리할 때 강력한 도구로 복잡한 상태 관리가 필요한 경우 유리하다
→ 사용 방식 :
▶ React Query :
useQuery와 useMutation 훅을 통해 비동기 데이터 요청을 수행,
기본적으로 API 응답 데이터를 캐싱해 필요시 재사용하거나 자동으로 최신 상태를 동기화함
데이터의 staleTime, 폴링(Polling), Retry 같은 서버 데이터 관련 기능이 내장
▶ Redux :
데이터의 상태를 전역적으로 관리하기 위해 **스토어(store)**와 액션(action) 및 **리듀서(reducer)**를 사용
상태변경은 명시적으로 dispatch를 통해 이루어짐
상태 관리의 유연성과 확장성이 뛰어나지만 초기 설정과 보일러플레이트 코드가 많을 수 있다
특징 | React Query | Redux |
상태 유형 | 서버 상태(Server State) | 전역 상태 (Global State) |
주요 사용 사례 | API 응답 데이터, 서버 동기화 데이터 | 사용자 인증 상태, UI 상태, 클라이언트 상태 |
데이터 캐싱 | 내장된 캐싱, 자동 재요청 지원 | 직접 구현 필요 |
비동기 작업 | 내장된 훅으로 쉽게 관리 가능(useQuerry, useMutation) | Redux Thunk 또는 Saga로 관리 가능 |
② React Query는 어떤 상황에서 적합한 상태 관리 도구일까?
→ 서버 데이터 관리가 주된 목적일 때 적합하다
주요 사용 사례는 API 응답 데이터와 서버 동기화 데이터
▶API 응답 데이터 : 클라이언트 애플리케이션이 서버로부터 가져오는 모든 데이터
ex) REST API, GraphQL API로부터 JSON 데이터를 가져와 화면에 표시하거나 내부 로직에서 사용
▶서버 동기화 데이터 : 클라이언트에서 처리된 변경 사항이 서버에 반영되고 그 결과로 다시 가져온 데이터가 최신 상태로 유지되는 것을 의미한다
'개발 기록 > front - react' 카테고리의 다른 글
React에서 Hook이란? (공부 + 면접 답변용) (1) | 2024.12.18 |
---|---|
[React] 함수형 컴포넌트 (0) | 2024.11.27 |
[React] React 컴포넌트 생명주기, 생명주기 메소드 (0) | 2024.11.26 |
[react] React 컴포넌트와 Props, State (1) | 2024.11.25 |
[JavaScript] 화살표 함수 = (arrow function) => {} (2) | 2024.11.20 |