TanStack 이라는 오픈소스에서 제공하는 라이브러리 중 한가지.
v3까지는 React Query 명칭을 사용했으나 TanStack Query(v4)라는 이름으로 브랜드 변경 및 mono repo로 구조 조정되었다.
Announcing TanStack Query v4!본 글에서는 React Query명칭을 사용하겠음.
**이제 구체적인 기능 하나하나에 대해 알아보자**
### [개발도구](https://tanstack.com/query/v4/docs/devtools)
$ npm i @tanstack/react-query-devtools
or
$ pnpm add @tanstack/react-query-devtools
or
$ yarn add @tanstack/react-query-devtools
- 패키지 설치를 통해 사용한다.
![](https://velog.velcdn.com/images/thinkanddoit/post/90aafb50-8a43-4342-b306-d84e6e22417e/image.png)
- React Query의 모든 내부 작동을 시각화해서 디버깅 시간 절약한다.
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() {
return (
{/* The rest of your application */}
//프로젝트 최상단(App)에 추가해서 사용한다.
)
}
- 2가지 모드, option들을 제공한다.
- 기본적으로 React Query Devtools는 `process.env.NODE_ENV === 'development'`에서만 번들에 포함되니 배포시 제외하는 것에 대해 고려하지 않아도 된다.
- 자세한 사항은 공식문서 및 참고를 확인하자.[(참고)](https://daily-dev-tips.com/posts/make-your-life-easier-with-react-query-devtools/#thank-you-for-reading-and-lets-connect)
### [TypeScript](https://tanstack.com/query/v4/docs/typescript)
- React Query는 TypeScript로 작성되어 있어서 type-safe를 보장한다.
#### Type Inference
- React Query에서 타입추론이 잘되기 때문에 타입지정을 따로 할 필요는 없다.
const { data } = useQuery(['test'], () => Promise.resolve(5))
// ^? const data: number | undefined
- 타입 추론은 `queryFn`에서 반환 타입에 대한 정의가 잘 되어있을때 효과가 있다. 보통 data fetching 라이브러리에서는 기본적으로 any타입을 결과로 반환하기 때문에 적절한 반환타입을 지정해야한다.
#### Type Narrowing
- React Query는 query 결과로 `state 필드`와 `파생된 state boolean flags`로 구분되는 [discriminated union](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions) type을 사용한다.
const { data, isSuccess } = useQuery(['test'], () => Promise.resolve(5))
// data의 정의여부 확인을 위해 isSucess를 확인해서 Type Narrowing을 수행.
if (isSuccess) {
data
// ^? const data: number (not undefined)
}
#### Typing the error field
- error의 타입은 기본적으로`unknown`이다. [참고)useUnknownInCatchVariables](https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/#use-unknown-catch-variables)
- 안전하게 error를 처리하려면 runtime에서 error를 체크하거나 명시적으로 error type을 지정해야한다.
[React Query and TypeScript 읽을 거리](https://tkdodo.eu/blog/react-query-and-type-script)
### [Important Defaults](https://tanstack.com/query/v4/docs/guides/important-defaults)
- React Query는 `default 값들`이 존재한다.
#### staleTime
- `useQuery`와 `useInfiniteQuery`를 한 Query instance는 캐시된 데이터를 `stale`로 여긴다.
- `stale`은 오래된 데이터라는 개념인 것 같음.
- `staleTime` option을 통해서 전역 or 쿼리별로 refetch 타이밍을 설정할 수 있다.
`stale` 상태인 query는 다음과 같은 경우 refetch를 자동적으로 수행한다:
>
- 새로운 query instance가 마운트 될때
- window가 다시 포커스될 때
- network가 다시 연결될 때
- 선택적으로 설정된 refetch interval 지점 때
*주의 : 개발시에는 `refetchOnWindowFocus`가 설정되어 있기때문에 DevTools과 app에서 포커스를 이동 할 때마다 refetch가 수행됨.
#### refetchOnMount, refetchOnWindowFocus, refetchOnReconnect, refetchInterval
- `refetchOnMount`, `refetchOnWindowFocus`, `refetchOnReconnect`, `refetchInterval` 옵션 설정을 통해 refetch 타이밍은 제어가 가능하다.
#### cacheTime
- 더이상 활성화되지 않은 `useQuery`와 `useInfiniteQuery`의 쿼리 결과는 `inactive`상태가 된다.
- `inactive`상태에서는 다시 사용할 경우를 대비해 캐시에 남아있는다.
- default로 `inactive`상태의 쿼리가 garbage collected되기까지 5분이 설정되어 있다.(`cacheTime` = 1000 * 60 * 5)
- `cacheTime` 옵션 설정을 통해 제어가 가능하다.
#### retry, retryDelay
- default로 쿼리는 실패시 3번의 재시도를 수행한다.
- `retry`, `retryDelay` 옵션 설정을 통해 제어가 가능하다.
### [Queries](https://tanstack.com/query/v4/docs/guides/queries)
서두
TanStack Query v4 공식문서
React Query란?
개요
그래서 무슨 라이브러리인가?
왜 사용해야하는가?
리액트에서는
데이터 패칭 & 업데이트
기능을 제공하지 않는다. 우리는 리액트 훅을 통해서 컴포넌트 단위로 상태를 직접 관리하거나 전역 라이브러리를 사용해서 전체 상태관리 및 비동기 데이터를 관리한다.기존 상태 관리 라이브러리는 클라이언트 상태관리에는 적합하지만 비동기 또는
서버상태
관리에는 제약이 있다.React Query vs SWR vs Apollo vs RTK Query vs React Router
서버 상태
를 파악하기 위해 다음과 같은 까다로운 기능구현이 필요하다.어려운 programming 영역
)out of date
유무 파악pagination
,lazy loading data
와 같은 최적화memoizing
그렇다면 React Query는 어떤 방식을 통해 위와 같은 서버상태 관리를 효율적으로 도와주는지 기능 분석을 통해서 알아보자
code snippet을 통해 3가지 개념 미리보기
// Client를 생성함. const queryClient = new QueryClient()
function App() { return ( // App(최상단)에서 Provider을 통해 Client 전달.
) }
function Todos() { // Client에 접근 const queryClient = useQueryClient()
// Queries const query = useQuery(['todos'], getTodos)
// Mutations const mutation = useMutation(postTodo, { onSuccess: () => { // 쿼리 데이터 무효화 및 refetch (Query Invalidation) queryClient.invalidateQueries(['todos']) }, })
return (
{query.data?.map(todo => (- {todo.title}
))}
) }
render( , document.getElementById('root'))
$ npm i @tanstack/react-query-devtools
or
$ pnpm add @tanstack/react-query-devtools
or
$ yarn add @tanstack/react-query-devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() { return (
) }
const { data } = useQuery(['test'], () => Promise.resolve(5)) // ^? const data: number | undefined
const fetchGroups = (): Promise<Group[]> => axios.get('/groups').then(response => response.data)
const { data } = useQuery(['groups'], fetchGroups) // ^? const data: Group[] | undefined
const { data, isSuccess } = useQuery(['test'], () => Promise.resolve(5)) // data의 정의여부 확인을 위해 isSucess를 확인해서 Type Narrowing을 수행. if (isSuccess) { data // ^? const data: number (not undefined) }
// runtime에서 error 체크
const { error } = useQuery(['groups'], fetchGroups) // ^? const error: unknown
if (error instanceof Error) { error // ^? const error: Error
}
// 명시적 Error type 지정
const { error } = useQuery<Group[], Error>(['groups'], fetchGroups) // ^? const error: Error | null