Search
Duplicate
📒

[React 완벽 가이드] 08-1. React Query

상태
수정중
수업
React 완벽 가이드
주제
4 more properties
참고

React Query

NOTE
React Query는 React에서 서버의 상태를 불러오고, 캐싱하며, 지속적으로 동기화하고 업데이트 하는 작업을 도와주는 라이브러리다!
codesandbox.io
reac-query vs redux
react-query 흐름도

설치

yarn add @tanstack/react-query@5.25.0 -D
Bash
복사
설치 코드

핵심 기능

캐싱
동일한 데이터에 대한 중복 요청을 단일 요청으로 통합
백그라운드에서 오래된 데이터 업데이트
데이터가 얼마나 오래되었는지 알 수 있다.
데이터 업데이트를 가능한 빠르게 반영
페이지네이션 및 데이터 지연 로드와 같은 성능 최적화
서버 상태의 메모리 및 가비지 수집 관리
구조 공유를 사용하여 쿼리 결과를 메모화

QueryClinet

NOTE
QueryClient는 주로 데이터 페칭, 캐싱 및 상태를 처리하는 핵심객체이다!
import { QueryClient, QueryClientProvider, useQuery, } from '@tanstack/react-query' const queryClient = new QueryClient() // 최상단에 Provier 제공 export default function App() { return ( <QueryClientProvider client={queryClient}> <Example /> </QueryClientProvider> ) }
TypeScript
복사
queryClinet 전역설정
queryClient를 사용하여 캐시와 상호 작용할 수 있다
queryClinet에서 모든 query, mutation에 기본 옵션을 추가할 수 있다.

cancleQuery

NOTE
cancleQuery는 아직 HTTP요청이 끝나지 않았을 때, 페이지를 벗어날 경우에도 중간에 취소해서 필요없는 네트워크 리소스개선을 할 수 있다.
const queryClient = useQueryClient(); const onCancelQuery = (e) => { e.preventDefault(); queryClient.cancelQueries({ queryKey: ["super-heroes"], // 해당 쿼리키를 가진 쿼리를 취소 }); }; return <button onClick={onCancelQuery}>Cancel</button>;
TypeScript
복사
query 요청취소

invalidateQueries

NOTE
invalidateQueries는 해당 query Key의 캐싱값을 강제로 무효화시킨다!
const useAddPost = () => { const queryClient = useQueryClient(); return useMutation(addPost, { onSuccess: (data) => { // 'posts' 쿼리 키에 해당하는 캐시 무효화 queryClient.invalidateQueries(["posts"]); console.log(data); }, onError: (err) => { console.error(err); }, }); };
TypeScript
복사
query 요청취소
주로 post/put/delete와 같이 데이터 변화가 생기는 경우 기존의 캐싱값을 무효화하기위해 사용한다.

getQueryData, setQueryData

NOTE
setQueryData는 쿼리의 캐싱 데이터를 업데이트하는 동기 함수이다!
queryClient.getQueryData('post'); queryClient.setQueryData('post', old => [...old, newTodo]);
TypeScript
복사
캐시 데이터 get/post
const useUpdatePostTitle = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: updatePostTitle, onSuccess: (data, variables) => { const { postId } = variables; queryClient.setQueryData(['post', postId], (oldData: any) => { return { ...oldData, title: data.data.title, // 새로운 제목으로 업데이트 }; }); }, onError: (err) => { console.error(err); }, }); };
TypeScript
복사
게시판의 제목을 수정하는 로직

useQuery - 자동 Query 요청

NOTE
useQuery페이지 변경, 컴포넌트 렌더링시 자동으로 호출되며, 데이터를 캐싱한다!
export function useQuery< TQueryFnData = unknown, // queryFn의 실행결과타입 TError = DefaultError, // queryFn의 실패타입 TData = TQueryFnData, // data에 담기는 데이터 타입(fn의 반환값이 재가공될 수도있음) TQueryKey extends QueryKey = QueryKey // key 타입 >
TypeScript
복사
useQuery 타입
const { data, error, status, fetchStatus, isLoading, isFetching, isError, refetch, // ... } = useQuery({ queryKey: ["super-heroes"], queryFn: getAllSuperHero, });
JavaScript
복사
주요 리턴 데이터
data: 쿼리 함수가 리턴한 Promise에서 나온 데이터
error: 쿼리 함수에 오류가 발생한 경우
status: data, 쿼리 결과값에 대한 상태를 표현하는 status는 문자열 형태로 3가지 값이 존재한다.
pending: data가 없고, 쿼리시도가 완료되지 않은 상태
error: 에러 발생했을때의 상태
success: 쿼리 함수가 오류 없이 요청 성공하고 데이터를 표시할 준비가된 상태
fetchStatus: queryFn에 대한 정보를 나타낸다.
fetching: 쿼리가 현재 실행중인 상태
paused: 쿼리를 요청했지만, 잠시 중단된 상태
idle: 쿼리가 아무런 작업도 수행하지 않은 상태
isLoading: 캐싱된 데이터가 없는경우, 로딩여부에 따라 true/false로 구분
isFetching: 캐싱된 데이터가 있더라도, 로딩여부에 따라 true/false로 구분
isSuccess: 쿼리 요청 성공여부
isError: 쿼리 요청 실패여부
refetch: 쿼리를 수동으로 다시 요청하는 함수

status vs fetchStatus

status: 쿼리의 전반적인 상태, 주로 data의 상태를 표현한다.
fetchStatus: 자동 리프레시, 사용자 상호작용에 의해 패칭되는 등 세부적인 동작 파악
즉 2개의 구분은 애플리케이션에서 데이터 패칭과 관련된 다양한 상황에 대응하기 위해 더 많은 옵션과 유연성을 제공하기위해서이다.

queryKey

NOTE
queryKey는 데이터를 고유하게 식별하며, 쿼리 함수 캐싱 기준으로 사용된다.
// 1) queryKey값은 queryFn함수에 인자값으로 전달할 수 있다. const getPostById = async ({ queryKey }: any) => { const postId = queryKey[1]; // postId 사용 return await axios.get(`http://localhost:4000/posts/${postId}`); }; // 특정 게시물의 데이터를 가져오기 위한 커스텀 훅 const usePostData = (postId: string) => { return useQuery({ queryKey: ["post", postId], // 게시물 조회를 위한 고유 queryKey 구성 queryFn: getPostById, }); };
TypeScript
복사
post id 1을 조회하는 useQuery
useQuery는 queryKey를 기반으로 쿼리 캐싱을 관리하는것이 핵심이다.
만약 쿼리가 특정 변수에 의존한다면 배열에다 이어서 작성해줘야 한다.
queryKey값에 설정한대로 이후에 queryClient.setQueryData를 이용할 때 동일한 key로 접근이 가능해진다.

queryFn

NOTE
queryFn은 데이터를 비동기적으로 패치하는 함수를 지정하는데 사용된다!
// 2) queryKey가 아닌 자체 인자를 받는 형태 const getPostById = async (postId : any) => { return await axios.get(`http://localhost:4000/posts/${postId}`); }; // 특정 게시물의 데이터를 가져오기 위한 커스텀 훅 const usePostData = (postId: string) => { return useQuery({ queryKey: ["post", postId], queryFn: () => getPostById(postId), // 데이터 패칭 함수(Promise 반환) }); };
TypeScript
복사

options

NOTE
useQuery의 option에 대해서는 공식문서를 참조하자!
// 특정 슈퍼히어로의 데이터를 가져오기 위한 커스텀 훅 const useSuperHeroData = (heroId: string) => { return useQuery( ["super-hero", heroId], () => getSuperHero(heroId), // 추가 옵션 { cacheTime: 5 * 60 * 1000, // 데이터가 캐시에 남아있는 시간: 5분 staleTime: 1 * 60 * 1000, // 데이터 신회성(stale) 시간: 1분 retry: 1, // 실패시 재시도 횟수: 1회 // 필요한 추가 옵션들을 여기에 설정할 수 있습니다. } ); };
TypeScript
복사
필수값은 단순 인자로 넘겨도된다. (option으로 넘겨도 상관없음)

options 종류

const { data, // ... } = useQuery({ queryKey: ["super-heroes"], queryFn: getAllSuperHero, gcTime: 5 * 60 * 1000, // 5분(삭제시간) staleTime: 1 * 60 * 1000, // 1분(새로운 페칭 요구시간) });
TypeScript
복사
staleTime, gcTime
const usePostsTitles = () => { return useQuery<string[]>({ queryKey: ['posts'], queryFn: fetchPosts, // 게시물 데이터에서 제목만 추출 select: (posts) => posts.map((post) => post.title), }); };
TypeScript
복사
select
import { useQuery } from 'react-query'; const PostsComponent = () => { const { data: posts } = useQuery<Post[], Error>({ queryKey: ['posts'], queryFn: fetchPosts, // 초기 로딩 시 표시될 임시 데이터, isLoading단계에서 출력됨 placeholderData: [ { id: -1, title: 'Placeholder Title 1', content: 'Placeholder content...' }, { id: -2, title: 'Placeholder Title 2', content: 'Placeholder content...' }, ], }); return ( <div> <h2>Posts</h2> <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ); };
TypeScript
복사
placeholderData

useMutation - 비동기 Query 요청

NOTE
useMutation은 React Query에서 비동기 업데이트 작업(데이터 생성, 업데이트, 삭제)를 수행할 수 있는 Hook이다. mutate를 실행시킴으로서, 비동기 이벤트를 발생시킬 수 있다.
const PostAdd = () => { const { mutate, // mutation 실행 함수 mutateAsync, // Promise를 반환하는 mutation 실행 함수 data, // mutation의 결과 데이터 error, // mutation 실행 시 발생한 오류 객체 isLoading, // mutation이 로딩 중인지 여부 (mutate 호출 후 완료될 때까지 true) isSuccess, // mutation이 성공적으로 완료되었는지 여부 isError, // mutation 실행 시 오류가 발생했는지 여부 status, // mutation의 상태 ('idle', 'loading', 'success', 'error' 중 하나) } = useMutation({ mutationFn: ,addPort // mutation을 실행할 함수 // 필요에 따라 onSuccess, onError, onSettled 등의 콜백 옵션을 추가할 수 있음 });
JavaScript
복사
예시 코드

Optimistic Update(낙관적 업데이트)

NOTE
Optimistic Update는 서버 업데이트시 결과를 기다리지 않고, 미리 업데이트하고 이후 서버 검증을 통해 업데이트/롤백을 하는 방식이다!
업데이트 흐름도
const useUpdatePostTitle = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: updatePostTitle, onMutate: async ({ postId, newTitle }) => { await queryClient.cancelQueries(['posts']); // 이전데이터 백업 const previousPosts = queryClient.getQueryData(['posts']); // 미리 업데이트 queryClient.setQueryData(['posts'], (oldPosts: any) => { return oldPosts.map(post => { if (post.id === postId) { return { ...post, title: newTitle }; } return post; }); }); return { previousPosts }; }, // 실패시 롤백 onError: (error, variables, context) => { queryClient.setQueryData(['posts'], context.previousPosts); }, // 성공/실패 후에 쿼리 무효화하고 최신화 갱신 onSettled: () => { queryClient.invalidateQueries(['posts']); }, }); };
TypeScript
복사