React Query 용법 1
Install
https://tanstack.com/query/latest/docs/framework/react/installation
- @tanstack/react-query 을 설치한다.
- Query Client 객체 싱글톤 인스턴스를 생성한다. 중앙 집중형관리이므로 해당 단일 객체를 주로 사용한다.
- Context Provider를 제공
npm i @tanstack/react-query
---
// src/libs/query-client.ts
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: 3,
staleTime: 0,
gcTime: 0,
},
},
});
---
export default function App() {
return (
<QueryClientProvider client={queryClient}>
...
</QueryClientProvider>
);
}
Basic Convention
역할 담당
- 개인적인 컨벤션으로 해당 lib 디렉터리에 useQuery, useMutation 들을 모아두는것을 좋아한다.
- 캐시 무효화 관리는 onSuccess에서 진행하는것을 추천.
useSuspenseQuery
- 장점 : 선언형으로 데이터 패칭, 애러처리, 로딩을 관리할 수 있다.
- useSuspenseQuery를 사용하는 컴포넌트는 데이터가 무조건 있다고 가정하고 로직을 전개해 간다.
// 애러 바운더리를 설치 (혹은 코드 직접 작성)
npm i react-error-boundary --save
---
// useSuspenseQuery 훅 선언하기
import { useSuspenseQuery } from '@tanstack/react-query';
import { TodoListItem } from 'types';
function useQueryTodos() {
// 제너릭 타이핑 <queryFn의 반환값, 애러 타입, 최종 반환 타입>
return useSuspenseQuery<TodoListItem[]>({
queryKey: ['todos'],
queryFn: async () => {
const todos = await getTodos();
return todos;
},
});
}
export default useQueryTodos;
---
// QueryErrorResetBoundary의 reset을 호출하면 retry를 시도한다.
// ErrorBoundary는 fallbackRender에서 전달해주는 resetErrorBoundary으로 리셋 이벤트를 전파한다.
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h3>할 일 목록을 불러오는 중 오류가 발생했습니다!</h3>
<button onClick={() => resetErrorBoundary()}>다시 시도</button>
</div>
)}
>
<Suspense fallback={<div>할 일 목록을 불러오는 중...</div>}>
<TodoList />
</Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
---
// TodoList 컴포넌트
const TodoList = () => {
const { data: todos } = useQueryTodos();
...
};
useMutation
import { useMutation } from '@tanstack/react-query';
import { queryClient } from '../query-client';
function useMutationDeleteTodo() {
return useMutation({
// mutationFn 에 래퍼 함수를 넣는다. TC에서 Spy로 테스트 가능.
mutationFn: ({ id }: { id: string }) => deleteTodo({ id }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}
export default useMutationDeleteTodo;
ErrorBoundary
📌 애러 바운더리 타입
export type ErrorBoundaryPropsWithComponent = ErrorBoundarySharedProps & {
fallback?: never;
FallbackComponent: ComponentType<FallbackProps>;
fallbackRender?: never;
};
1.ComponentType vs Renderer
- 타입스크립트 관점에서 ComponentType는 클래스 컴포넌트를 포함하는 타입이다.
// ComponentType<FallbackProps>
type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;
// FunctionComponent<FallbackProps>
type FunctionComponent<P = {}> = (props: P) => ReactElement | null;
// 이렇게 하면 TypeScript 에러
<ErrorBoundary
FallbackComponent={({ error, resetErrorBoundary }) => <div>Error</div>} // ❌ 타입 에러
>
// 컴포넌트 참조를 넘겨주거나
<ErrorBoundary FallbackComponent={MyComponent}>
// 혹은 fallbackRender에 render function을 넘겨준다.
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => <div>Error</div>} // ✅ 타입 에러 없음
>
2.fallback ( React.Element )
// 아래처럼 리액트 엘리먼트를 넘겨준다.
<ErrorBoundary
fallback={<div>There was an error!</div>} // 정적 JSX만 가능
>