다음 영상을 보고 정리했습니다.
React Query와 상태관리 :: 2월 우아한테크세미나
주어진 시간에 대해 시스템을 나타내는 것으로 언제든지 변경될 수 있다.
개발자에겐 관리해야할 데이터
프론트엔드라는 분야가 구분될만큼 사용자에게 보여지는 부분이 많아졌다.
관리해야할 상태가 더 많아짐
안그래도 계속해서 변경되는 상태가 양까지 많아졌다.
연사님이 당시에 마주한 문제는 전역 상태 Store 에서 상태관리보다는 API 통신 코드가 많았다는 것이다.
이러한 현상을 유발하는 상태들의 특성을 다시 정리해야했다.
이래서 원격으로부터 받아온 상태는 일종의 캐시처럼 존재한다.
상태의 소유권으로 상태를 구분할 수 있게 되었다.
그 특성을 정리하자면 아래와 같다.
클라이언트 상태 (소유권이 클라이언트에게)
서버 상태 (소유권이 서버에게)
이러한 문제를 해결하기 위해 나온 라이브러리가 React Query
React Query 홈페이지에선 다음과 같이 설명한다.
Performant and powerful data synchronizatioiin for React
효과적이고 강력한 데이터 동기화툴
Fetch, cache and update data in your React and React Native applications all without touching any "global state"
리액트 환경에서 전역 상태를 건드리지 않고 캐시를 동기화한다.
원격에 있는 상태를 클라이언트와 동기화시켜주는 기술이라 볼 수 있다.
특징적으로는 선언적이며 React Hook 과 유사한 인터페이스로 친숙함, 그리고 Zero Configuration 으로 간편하고 기능적으로 강력하다.
공식 예제 코드
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
function Example() {
const { isPending, error, data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://api.github.com/repos/TanStack/query').then((res) =>
res.json(),
),
})
if (isPending) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}
React Query 를 사용하려면 QueryClientProvider 는 필수
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
Queries
data
: 마지막으로 성공한 resolved된 데이터 (Response)error
: 에러가 발생했을 떄 반환되는 객체isFetching
: Request 가 in-flight 중일 때 truestatus
, isLoading
, isSuccess
, isLoading
등 : 현재 query 의 상태refetch
: 해당 query refetch 하는 함수 제공remove
: 해당 query cache 에서 지우는 함수 제공기타 등등
: 이 있닷.onSuccess
, onError
, onSettled
: query fetching 성공 / 실패 / 완료 시 실행할 Side Effect 정의enabled
: 자동으로 query 를 실행시킬지 말지 여부retry
: query 동작 실패 시 , 자동으로 ㄱetry 할지 결정하는 옵션select
: 성공 시 가져온 data 를 가공해 전달keepPreviousData
: 새롭게 fetching 시 이전 데이터 유지 여부refetchInterval
: 주기적으로 refetch 할지 결정하는 옵션 (폴링 시 유리)기타 등등
const todo = useQuery('todo', () => {
return axios.get('/todos')
});
Mutations
mutate
: mutation 을 실행하는 함수mutateAsync
: mutate 와 비슷하지만 Promisereset
: mutation 내부 상태 clearonMutate
: Mutation 동작 전 먼저 동작하는 함수, Optimistic update 적용 시 유용Optimistic update 는 좋아요처럼 성공을 낙관적으로 판단해 API 응답 전에 성공 처리하는 기법 , 롤백 가능
const mutation = useMutation(newTodo => {
return axios.post('/todos', newTodo)
});
QueryInvalidation
// Invalidate every query in the cache
queryClient.invalidateQueries();
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries('todos')
위 자료에선 Opton 에 cacheTime, staleTime 이 있었다.
refetchOnWindowFocus, refetchOnMount 같은 것도 있었다.
React Query 의 refetch 메커니즘은 RFC 5861 의 개념을 참고하고 있습니다.
RFC 5861 (HTTP Cache-Control Extensions for Stale Content)
Cache-Control: max-age=600, stale-while-revalidate=30
이렇게 동작하면 Latency(지연시간) 가 숨겨진다.
(stale-if-error 도 이 명세에 존재, 요건 실패 시 이전 값 제공)
요약하자면 max-age=600, stale-while-revalidate=30 인 경우 캐싱 중인 값이 600초가 넘으면 새로운 값을 가져오는데 이때 stale 기간 30초까진 이전 값을 제공하며 백그라운드에서 새롭게 값을 가져온다는 뜻입니다.
이런 컨셉을 메모리 캐시에도 적용한다. (react-query, swr, etc)
cacheTime : 메모리에 얼마만큼 있을건지 (해당 시간 이후 GC 에 의해 처리, default 5분)
staleTime : 얼마의 시간이 흐른 후에 데이터를 stale 취급할 것인지 (default 0)
refetchOnMount, refetchOnWindowFocus, refetchOnReconnect 들이 true 일 경우 Mount, window focus, reconnect 시점에 data 가 stale 이면 모두 refetch (모두 default true)
활성화를 고려할 경우 (꺼졌다, 켜졌다하는 UI)
표시되진 않을 때 inactive 가 되며 다시 활성화되면 상태에 따라서 동작합니다. stale 상태라면 refetch 가 동작할 수 있습니다.
관련 기본값
staleTime
-> 0 (Queries 에서 cached data 는 언제나 stale 취급)
refetchOnMount
, refetchOnWindowFocus
, refetchOnReconnect
-> true (각 시점에서 stale 이라면 항상 refetch)
cacheTime
-> 60 5 1000 (inActive Query 들은 5분 뒤 GC 에 의해 처리)
retry
-> 3 (Query 실패 시 3번까지 retry)
retryDelay
-> exponential backoff function