React-Query 사용법 및 쓰는 이유
React-Query는 이전 React 과제를 할 때 아무것도 모른 상태에서 사용해서 장점을 크게 깨닫지 못했었습니다.
그때 당시에 React-Query를 왜, 어떤 상황에서 사용하는지, 장점이 무엇인지 알고 넘어갔어야 했는데 그저 이런 라이브러리가 있구나 ~ 하고 넘어갔었습니다.
사내 기술 세션에서 React-Query 관련 이야기가 나오고, 발표도 해주셔서 관심이 가고, 도입하려면 스터디가 필요하다는 생각에 따로 공부해야겠다고 마음먹고 적게 되었습니다.
우선 React-Query의 장점은 많지만, 아래 장점들 때문에 프로젝트에서 사용하고 싶었습니다.
- 서버 데이터 캐싱
- 데이터 패칭 시 로딩, 에러 처리를 한 곳에서 처리 가능
- prefetching, retry 등 다양한 옵션
- 쉬운 상태 관리
들어가기 전에
React Query의 라이프 사이클
- fetching - 데이터 요청 상태
- fresh - 데이터가 프레시한(만료되지 않은) 상태.
- 컴포넌트의 상태가 변경되더라도 데이터를 다시 요청하지 않는다.
- 새로고침 하면 다시 fetching 한다.
- stale - 데이터가 만료된 상태.
- 데이터가 만료되었다는 것은 서버에서 한번 프론트로 데이터를 주면 그 사이에 다른 유저가 데이터를 추가, 수정, 삭제 등등 할 수 있기 때문에 만료되었다고 한다. (최신화가 필요한 데이터)
- 컴포넌트가 마운트, 업데이트되면 데이터를 다시 요청합니다.
- fresh에서 stale로 넘어가는 시간 -> 기본값 0
- inactive - 사용하지 않는 상태.
- 일정 시간이 지나면 가비지 콜렉터가 캐시에서 제거함
- 기본값 5분
- delete - 가비지 콜렉터에 의해 캐시에서 제거된 상태.
fetching -> fresh -> stale -> inactive -> delete
아래 포스팅에서 이해하기 쉽게 정리해주셨습니다.
-> React Query에서 staleTime과 cacheTime의 차이
사용법
소스 코드 - https://github.com/DinnerKang/study_react/tree/main/packages/react-query
1. React-Query 준비
상위에서 QueryClient를 생성 후 넣어줍니다.
ReactQueryDevtools는 React-Query의 개발도구입니다.
function App() {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
<div className="App">
<Users />
</div>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
2. 데이터 패칭
useQuery를 이용해서 데이터를 불러올 수 있습니다.
특징으로는 React Hook을 활용한다는 점과 queryKey가 캐싱의 핵심이며 상태 관리도 이 부분을 이용해서 할 수 있을 것 같습니다.
// API 호출
const getUserWithAxios = async () => {
const { data } = await axios.get('http://localhost:8000/users');
return data;
};
import {
useQuery,
} from 'react-query';
const Users = () => {
// 'users' 라는 queryKey값
const query = useQuery('users', getUserWithAxios);
// query 안에 data, isLoading, isSuccess, isError 등 다양한게 있다.
console.log(query);
return (
{
!query.isLoading && (
query.data.map((i: User) => (
<li key={i.id}>{i.name}</li>
))
)
}
);
}
API를 기다리는 동안은 loading 상태인데, 성공할 경우 값이 들어오는 것을 볼 수 있습니다.
3. 데이터 업데이트
useMutation을 사용해서 데이터를 업데이트합니다.
const Users = () => {
const [userId, setUserId] = useState<number>(5);
// Query
const { isLoading, data, isError } = useQuery('users', getUserWithAxios, {
staleTime: 5000,
});
const mutation = useMutation((data) => axios.post('http://localhost:8000/user', data), {
onSuccess: () => {
setUserId(userId + 1);
},
});
const handleSubmit = useCallback(
(data) => {
mutation.mutate(data);
},
[mutation],
)
if (isError) return <div>에러</div>;
return (
<ul>
{
!isLoading && (
data.map((i: User) => (
<li key={i.id}>{i.name}</li>
))
)
}
<button onClick={() => handleSubmit({id: userId, name: `test${userId}`})}>유저 추가</button>
</ul>
);
};
여기서도 중요한 것이 있습니다. 그냥 useMutation을 통해서 데이터만 업데이트하게 되면, 서버에만 데이터 수정사항을 보내는 것입니다.
프론트에서는 데이터가 fresh하지 않기 때문에 test5가 추가된 것을 모릅니다.
이럴 경우 사용자 경험이 좋지 않습니다!
보통 이럴 경우에는 2가지 방법이 있는데
1. get을 한번 더 해서 리스트를 초기화한다.
2. useState를 활용, 기존 Array에 push 해서 setState를 해준다.
1안 같은 경우에는 서버의 리소스를 사용하는 거라 지양하고 2안을 지향합니다.
하지만 2안을 하려면 매우 귀찮은 짓을 해야 하는데, React Query에서는 그나마 편하게 바꿔줄 수 있습니다.
const queryClient = useQueryClient();
const [userId, setUserId] = useState<number>(5);
// Query
const { isLoading, data, isError } = useQuery('users', getUserWithAxios, {
staleTime: 5000,
});
const mutation = useMutation((data: User) => axios.post('http://localhost:8000/user', data), {
onMutate: (data: User) => {
const previousValue = queryClient.getQueryData('users');
console.log('previousValue', data);
queryClient.setQueryData('users', (old: any) => {
console.log('old', old);
return [...old, data];
});
return previousValue;
},
onSuccess: (result, variables, context) => {
console.log('성공 메시지:', result);
console.log('변수', variables);
console.log('onMutate에서 넘어온 값', context);
setUserId(userId + 1);
},
});
useMutation Hook의 onMutate에서 setQueryData를 이용해 해당 키의 데이터를 수정해주는 방식입니다.
이렇게 하면 사용자 경험이 매우 좋아질 수 있고, 필요 없는 네트워크 비용을 줄일 수 있습니다.(엄청 중요한 리스트면 다시 불러야겠죠?)
그럴 경우 네트워크를 타지 않고 프론트에서 추가된 값을 보여줄 수 있습니다.
기본적인 데이터 패칭, 업데이트만 조사해봐도 React-Query의 장점이 많이 보이는 것 같습니다.
참고 글
2021-08-27 React Query Tutorial 01 소개