요약
리액트 쿼리는 서버 상태를 가져오거나 업데이트, 캐싱 및 동기화를 손쉽게 해주는 라이브러리입니다.
리액트 쿼리를 사용했을 때의 장점은 많은 코드들로 예측이 힘들어지는 사이드 이펙트들을 간단하고 명확한 로직으로 대체하여 코드 구조를 개선할 수 있다는 점, 캐싱 처리를 손쉽게 할 수 있다는 점, 웹 앱의 상태를 서버와 원활하게 동기화 시킬 수 있다는 점 등이 있습니다.
React Query?
React Query는 웹 애플리케이션에서 서버 상태를 가져오기/업데이트, 캐싱, 동기화 등의 작업을 손쉽게 해줍니다.
이점
- 많은 서버 관련 코드들로 인해 예측하기 힘들어지는 사이드 이펙트를 간단하고 명확한 React Query 로직으로 대체하여 코드 구조를 개선할 수 있습니다.
- 캐싱 처리가 간편합니다.
- 여러 유용한 기능을 제공합니다.
- 웹 애플리케이션 상태를 서버와 원활하게 동기화할 수 있습니다. 항상 최신 데이터를 UI에 띄울 수 있습니다.
Query와 Mutation
- Query: 서버에서 데이터를 가져오는 작업을 주로 함, 주로 useQuery 훅을 사용, GET 요청
- Mutation: 서버의 데이터를 변경하는 부수효과가 있는 작업을 주로 함, 주로 useMutation 훅을 사용, POST-PUT-PATCH-DELETE 요청
- useQueries: 여러 데이터를 병렬로 가져와야 할 때 사용
- useInfiniteQuery: 무한 스크롤처럼 연속적으로 데이터를 가져와야 할 때 사용
Query는 기본 값 기준으로
- 쿼리를 사용한 컴포넌트가 마운트 되었을 때
- 윈도우가 다시 포커스 되었을 때
- 네트워크가 재연결 되었을 때
- refetchInterval 설정으로 반복적인 refetch가 일어날 때
자동으로 데이터를 가져옵니다.
retry
retry 옵션이 설정되면 Query 또는 Mutation 작업이 실패했을 때 자동으로 재시도를 합니다.
기본 값은 0입니다.
staleTime
Query를 통해 가져온 데이터가 오래된 데이터인지 판단하는 시간입니다. ms 단위로 저장되며, 기본 값은 0입니다.
- staleTime: 0
데이터를 가져오는 즉시 오래된 데이터라고 판단한 뒤, 캐시된 데이터를 사용한 후 API를 재호출하여 응답 받은 데이터를 교체합니다.
- staleTime: 2000
2초 이전에 데이터를 요청했다면 캐시된 데이터를 사용합니다.
2초 이후로 데이터를 요청했다면 응답받은 데이터로 교체한 뒤 캐시합니다.
cacheTime
데이터를 얼마나 보관할 것인지 나타내는 시간입니다. ms 단위로 저장되며, 기본 값은 5분(5*60*1000)입니다.
쿼리 인스턴스가 언마운트되면 비활성화 상태가 되는데, 비활성화된 데이터는 cacheTime에 설정된 시간이 지난 후 가비지 컬렉션이 됩니다.
- cacheTime: 2000
2초 이전에 데이터를 요청했다면 캐시된 데이터를 사용하고 API를 호출하여 응답 받은 데이터를 캐시합니다.
2초 이후로 데이터를 요청했다면 캐시했던 데이터는 가비지 컬렉션이 되었기 때문에 사용이 불가능하여 API를 호출하여 응답받은 데이터를 사용하고 캐시합니다.
queryKey
Query는 queryKey를 기반으로 캐싱을 관리합니다.
useQuery({ queryKey: ['todos'], ... }) useQuery({ queryKey: ['something', 'special'], ... }) useQuery({ queryKey: ['todo', 5], ... }) useQuery({ queryKey: ['todo', 5, { preview: true }], ...}) useQuery({ queryKey: ['todos', { type: 'done' }], ... })
queryKey는 배열의 순서로 동일한 쿼리인지 판단합니다. 중첩된 객체의 순서는 동일한 쿼리로 판단하지만, 배열의 순서가 다르면 다른 쿼리로 판단합니다.
// 동일한 쿼리 useQuery({ queryKey: ['todos', { status, page }], ... }) useQuery({ queryKey: ['todos', { page, status }], ...}) useQuery({ queryKey: ['todos', { page, status, other: undefined }], ... }) // 다른 쿼리 useQuery({ queryKey: ['todos', status, page], ... }) useQuery({ queryKey: ['todos', page, status], ...}) useQuery({ queryKey: ['todos', undefined, page, status], ...})
캐싱 라이프 사이클
A라는queryKey를 가진A쿼리 인스턴스가mount
- 네트워크를 통해 데이터를 가져오고(
fetch), 가져오는 데이터는A라는queryKey로 캐싱
- 이 데이터는 신선한(
fresh) 상태에서staleTime이후 오래된(stale) 상태로 변경
A쿼리 인스턴스가unmount
- 캐시는
cacheTime만큼 유지하다가 가비지 컬렉션
- 만약
cacheTime이 지나기 전, A 쿼리 인스턴스가 신선한(fresh) 상태라면 새롭게mount되었을 때 캐시된 데이터를 보여줌
useQuery의 유용한 옵션
select
queryFn에서 반환된 데이터를 변경하거나 선택해서 반환할 수 있습니다.
const QuerySelect = () => { const { data } = useQuery({ queryKey: ["project"], queryFn: () => { return new Promise((resolve) => { setTimeout(() => resolve([1, 2, 3, 4, 5, 6]), 2000); }); }, select: (value: any) => value.filter((x: any) => x % 2 === 0) }); return <div>{data?.join(",")}</div>; }; export default QuerySelect;
enabled
true로 설정하면 구성요소가 마운트되거나 쿼리 키가 변경될 때 자동으로 실행됩니다. false로 설정하면 자동으로 실행되지 않습니다. useQuery에서 반환되는 refetch 함수를 사용하여 수동으로 트리거할 수 있습니다. 기본값은 true입니다.
const QueryEnable = () => { const [isEnable, setEnable] = useState(false); const { data } = useQuery({ queryKey: ["project"], queryFn: () => { return new Promise((resolve) => { setTimeout(() => resolve([1, 2, 3, 4, 5, 6]), 2000); }); }, enabled: isEnable }); useEffect(() => { setTimeout(() => setEnable(true), 2000); }, []); return <div>{data?.join(",")}</div>; }; export default QueryEnable;
keepPreviousData
페이징 처리할 때 유용한 옵션입니다. 쿼리가 새로운 데이터를 가져오기 전까지 이전 데이터를 유지시킵니다.