참고
React Query
NOTE
React Query는 React에서 서버의 상태를 불러오고, 캐싱하며, 지속적으로 동기화하고 업데이트 하는 작업을 도와주는 라이브러리다!
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
복사