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() {
// useQuery(query key, ...);
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() {
// useQuery(query key, query function);
const info = useQuery(query key, **fetcher**);
//...
}
promise를 반환하는 함수
api fetch함수가 들어간다.
Query Options
import { useQuery } from 'react-query';
function App() {
// useQuery(query key, query function, query option);
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 메서드 호출
// Invalidate every query in the cache
queryClient.invalidateQueries()
// Invalidate every query with a key that starts with 'todos'
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전략 사용 시 효율적