- React Application에서 서버 상태 관리(Caching, Continous Synchronization, etc…)의 도움을 주는 라이브러리이다.
- api요청을 보낼 때, React의 Local state와 Server state를 동기화하게 되는데 이때 편의성을 준다.
Client state vs Server state
Client state
- 클라이언트 소유
- 클라이언트에서 제약없는 제어가 가능함
- Client내 UI/UX 흐름 또는 사용자 인터렉션에 따라 변할 수 있음
- Client내에서 최신 상태로 관리됨
Server state
- 서버의 상태, 클라이언트 입장에서는 원격의 공간에서 제어되는 상태
- Fetching / Updating에 비동기 API가 필요
- 다른 클라이언트들과 공유되며, 클라이언트가 모르는 사이 변경될 수 있음
- 신경쓰지 않을 경우, out of date가 될 잠재적인 가능성이 있음
단순 API request
- 단순한 요청, XMLHttpRequest, fetch, axios 등을 실행한 요청
- React 컴포넌트 내에서 서버 상태를 동기화 하게 되며 코드가 장황해짐(state drilling, loading 등)
- 전역 상태 관리 시스템을 이용한 API
- 서버 상태를 React 컴포넌트 외부에서 관리
- redux를 사용할 경우 redux 특징인 작성되는 코드가 길어짐
- React-query
- 상대적으로 간결한 문법을 제공
- 각종 옵션으로 편의성을 제공(interval polling, Garbage collection 설정 등)
- Caching으로 빠른 성능 제공
- 불필요한 다중 요청 방지 제공
react-query의 컨셉
- RFC 5861 (Request for Comments - 인터넷 개발에 필요한 기술, 결과, 절차 등 메모해놓은 문서)
- 이 컨셉을 메모리 캐시에 적용 시도
- 결과물 : react-query, swr, …
- react-query에서,,
- cacheTime : 데이터가 메모리에 얼마 동안 존재할 것인가(해당 시간 이후 GC가 제거, default 5분)
- staleTime : 얼마의 시간이 흐른 후 데이터를 stale 취급할 것인가 (default 0)
- react-query stale 옵션
- refetchOnMount : mount시점에 데이터를 stale 취급
- refetchOnWindowFocus : window의 focus 시점에 데이터를 stale 취급
- refetchOnReconnect : reconnect 시점에 데이터를 stale 취급
- stale이라 판단되면 refetch (default : true)
Query life-cycle

- query 인스턴스가 mount됨
- stale time이 0보다 크다면 fresh상태
- stale time만료 시 stale 상태가 됨
- 스크린에서 사용되는 동안 stale 상태는 지속된다.
- 스크린에서 사용되지 않는다면 query는 inactive 상태가 됨
- cache time이 만료되기 전까지 메모리 상에서 존재함
- cache time이 만료될 경우 GC가 제거
- inactive 상태였다가 다시 스크린에 나타나야 될 경우 refetchOnMount 상태가 되며, refetch 실행
react-query default config
- Queries에서 Cached data는 언제나 stale 취급
- 각 시점에서 data가 stale이라면 항상 refetch 발생
- Inactive query들은 5분 뒤 GC가 제거
- Query 실패 시 최대 3번 retry 발생
react-query 데이터
- stale time은 30s로 설정
- 컴포넌트A에서 api-1을 호출
- 10초뒤 컴포넌트B에서 api-1을 호출
- 같은 api-1을 호출하지만 컴포넌트B에서 api-1호출은 실행되지 않는다.
- Context API를 활용한 전역상태로 관리되는 데이터
- QueryClient 내부적으로 Context API를 사용함
react-query 사용 방법
- Application에서 사용할 부분에 QueryClientProvider를 설정한다.
import { QueryCLient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient();
function App() {
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}
useQuery
Query Key
import { useQuery } from 'react-query';
function App() {
const info = useQuery(**query key**, fetcher);
}
- react query는 query key에 따라 캐싱을 관리한다.
- 데이터는 query key와 매핑된다.
- 데이터 타입은 string, Array이 될 수 있으며, react-query v4(beta)부터는 array 타입만 허용한다고 한다.
- query key는 해싱 되며, 아래의 키는 모두 같은 것으로 간주된다.
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)
- 하지만 아래의 형태는 같은것으로 간주되지 않는다. (배열의 순서가 중요)
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
Query function
import { useQuery } from 'react-query';
function App() {
const info = useQuery(query key, **fetcher**);
}
- promise를 반환하는 함수
- api fetch함수가 들어간다.
Query Options
import { useQuery } from 'react-query';
function App() {
const info = useQuery(query key, fetcher, **option**);
}
- useQuery를 사용할 때 옵션 설정
- onSuccess, onError, onSettled
- 데이터 타입(순서대로) : (data: TData) ⇒ void, (error: TError) ⇒ void, (data?: TData, error?: TError) ⇒ void
- query fetching 성공 실패 완료 시 실행할 side effect 정의
- enabled
- 데이터 타입 : boolean
- 자동으로 query를 실행 할지 여부
- retry
- 데이터 타입 : boolean | number | (failureCount: number, error: TError) ⇒ boolean
- 실패한 쿼리 재시도 옵션
- true설정 시 무한 재시도, false설정 시 재시도x
- default = 3번
- select
- 데이터 타입 : (data: TData) => unknown
- 성공 시 가져온 데이터 가공
- keepPreviousData
- 데이터 타입 : boolean
- 새롭게 fetching시 이전 데이터 유지 여부
- refetchInterval
- 데이터 타입 : number | false | ((data: TData | undefined, query: Query) => number | false)
- polling 사용 여부
- number타입 설정 시 해당 쿼리가 ms 단위로 refetch
- function설정시 최신 데이터로 함수가 실행, 빈도를 계산하는 쿼리 실행
- default = false
- staleTime (number | Infinity)
- 데이터가 fresh상태로 유지되는 시간 (fresh상태일 경우 쿼리가 다시 마운트 되어도 fetch는 실행되지 않음)
- 해당 시간 초과 시 stale 상태로 변경됨
- default = 0
- etc..
useMutation
- 데이터를 업데이트 할때 사용(post, put, delete)
- useQuery는 즉시 실행되지만, useMutation은 즉시 실행되지 않는다.
- useQuery는 상태를 공유하지만, useMutation은 그렇지 않다
- useQuery는 다른 컴포넌트에서 동일한 useQuery를 여러번 호출할 수 있으며, 캐시된 결과를 동일하게 반환하지만, useMutation에는 그런 기능이 없다.
const { data, error, isError, isLoading, mutate, mutateAsync, reset ... } =
useMutation(mutationFunction, options);
- 반환 값
- mutate
- mutateAsync
- mutate와 비슷하지만 promise를 반환
- reset
- etc…
- options
- onMutate
-
데이터 타입 : (variables) ⇒ Promise<TContext | void> | TContext void
-
본격적인 Mutation동작 전에 먼저 동작하는 함수
-
Optimistic update 적용시 유용함
Optimistic update
- 낙관적 업데이트
- UI에서 어차피 업데이트 할 것이란 가정으로 UI를 업데이트 후 서버를 통한 검증
- 실패한다면 롤백
- onMutateAsync
- etc…
Query Invalidation
- queryClient를 통해 invalidate 메서드 호출
queryClient.invalidateQueries()
queryClient.invalidateQueries('todos')
- 해당 key를 가진 query는 stale 취급
- 현재 rendering되고 있는 query는 백그라운드에서 refetch된다.
query 파일 분리
- 컴포넌트에서 직접 useQuery를 호출한다면 관리의 어려움이 있을 수 있다.
- query fetching역할의 파일을 따로 분리하는것 권장한다.
- 배민 주문시스템의 웹뷰에서 데이터 패칭을 할 때 도메인 별로 묶어서 관리한다고 한다.
query 상태
- fetching : 데이터 요청 상태
- fresh : 데이터가 만료되지 않은 상태
- stale : 데이터가 만료된 상태
- inactive : 사용하지 않는 상태
- delete : GC에 의하여 Cache에서 제거된 상태
react-query 사용상 장점
- 서버 상태 관리 용이 (redux, mobx보다 직관적인 API 호출, Caching 으로 빠른 성능 지원)
- API 처리에 대한 각종 인터페이스, 옵션 제공
- Client store는 클라이언트의 상태만 남아서 store답게 사용됨
- Cache전략 사용 시 효율적