1. React-Query 란?
React-Query란 서버의 데이터를 가져오거나, 캐싱, 값 동기화 및 업데이트, 에러 처리 등 비동기 과정을 쉽게 할 수 있도록 도와주는 라이브러리이다.
1.1 사용 이유
기존 상태 관리 라이브러리(redux 등)는 클라이언트 상태 관리에는 용이하지만, 서버 상태를 관리하기는 어렵다.
클라이언트에서 사용하는 상태가 변할 수 있는 서버의 상태와 다를 수 있기 때문이다.
즉, API 요청과 비동기 데이터 관리를 좀더 쉽게 하기위해 등장한 것이다.
React-Query는 서버의 상태를 불러오고, 불러온 상태를 캐싱하며, 지속적으로 동기화하고 업데이트 하는 작업을 도와준다.
또한 기존 상태 관리 라이브러리를 이용하면 서버의 데이터와 클라이언트 데이터를 store에 공존하게 될 수 있다.
(이에 대해서는 해당 문제에 대해 정리한 글이 있다.)
1.2 React-Query 장점
- 캐싱을 해준다.
- get한 데이터에 대해 update 하면 자동으로 get을 재실행해준다.
(게시판 글을 가져오고 글을 생성하면 글을 가져오는 api를 자동으로 재실행) - 데이터가 오래 되었다고 판단하면 다시 get한다.(invalidateQueries)
- 동일한 데이터 요청은 한번만 요청한다.
(옵션에 따라 중복 호출 허용 시간 조절이 가능하다.) - 무한 스크롤 기능을 쉽게 구현할 수 있다.(Infinite Queries)
- 비동기 과정을 선언적으로 관리할 수 있다.
- Reack Hook과 사용하는 구조가 유사하다.
2. React-Query 사용하기
React-Query를 사용하기 위해 초기 세팅을 시작하자.
npm i react-query
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
const queryClient = new QueryClient();
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={true} />
<App />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
2.1 useQuery
HTTP 메서드 중 GET 요청과 같이 서버에 저장되어 있는 "상태"를 불러와 사용 할 때 사용한다.
const { data } = useQuery(
queryKey, // 이 Query 요청에 대한 응답 데이터를 캐시할 때 사용할 Unique Key (required)
fetchFn, // 이 Query 요청을 수행하기 위한 Promise를 Return 하는 함수 (required)
options, // useQuery에서 사용되는 Option 객체 (optional)
);
useQuery는 Unique Key(Query Key)를 이용해 가져온 서버 상태를 로컬에 캐시하고 관리할 수 있다.
다른 컴포넌트에서 해당 Unique Key를 사용하는 useQuery Hook이 있으면 캐시된 데이터를 우선적으로 사용한다.
queryKey는 문자열과 배열을 전달 할 수 있는데, 배열로 넘기면 0번 값은 string값으로 다른 컴포넌트에서 부를 값이 들어가고 두번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달된다.
fetchFn은 비동기 함수(api 호출 함수)가 들어간다.(Promise가 들어가야 한다)
const Todos = () => {
const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, {
// react-query는 사용자가 사용하는 윈도우가 다른 곳을 갔다가
// 다시 화면으로 돌아오면 이 함수를 재실행합니다. 그 재실행 여부 옵션 입니다.
refetchOnWindowFocus: false,
retry: 0, // 실패시 재호출 몇번 할지
onSuccess: data => {
// 성공시 호출
console.log(data);
},
onError: e => {
// 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출됩니다.)
// 강제로 에러 발생시키려면 api단에서 throw Error 날립니다.
// (참조: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default)
console.log(e.message);
}
});
if (isLoading) {
return <span>Loading...</span>;
}
if (isError) {
return <span>Error: {error.message}</span>;
}
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
};
function Todos() {
const { status, data, error } = useQuery("todos", fetchTodoList);
if (status === "loading") {
return <span>Loading...</span>;
}
if (status === "error") {
return <span>Error: {error.message}</span>;
}
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
useQuery는 비동기로 동작해서 한 컴포넌트에 여러개의 useQuery가 비동기적으로 동시에 실행된다.
여러개의 useQuery를 사용하기 보다는, useQueries를 사용해 구현하는 것이 좋다.
useQuery 동기적으로 실행
enabled 옵셔을 사용하면 useQuery를 동기적으로 사용할 수 있다.
const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList);
const { data: nextTodo, error, isFetching } = useQuery(
"nextTodos",
fetchNextTodoList,
{
enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다
}
);
2.2 useQueries
useQuery를 여러번 사용해야 하는 경우에 사용해주면 좋다.
unique key를 배열로 넣으면 query 함수 내부에서 변수로 사용할 수 있다.
const test = {
version: "12.1.1"
};
const result = useQueries([
{
queryKey: ["getRune", test.version],
queryFn: params => {
console.log(params); // {queryKey: ['getRune', '12.1.1'], pageParam: undefined, meta: undefined}
return api.getRunInfo(test.version);
}
},
{
queryKey: ["getSpell", test.version],
queryFn: () => api.getSpellInfo(test.version)
}
]);
2.3 QueryCache
쿼리에 대한 성공 실패 전처리를 할 수 있다.
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
console.log(error, query);
if (query.state.data !== undefined) {
toast.error(`에러!!: ${error.message}`);
},
},
onSuccess: res => {
console.log(res)
}
})
});
onError와 onSuccess를 이용해 전처리를 할 수 있다.
2.4 useMutation
const { mutate } = useMutation(
mutationFn, // 이 Mutation 요청을 수행하기 위한 Promise를 Return 하는 함수 (required)
options, // useMutation에서 사용되는 Option 객체 (optional)
);
값을 바꿀때 사용하는 api이며, HTTP 메서드 중 PUT, POST, DELETE 요청과 같이 부수효과를 발생시켜 서버의 상태를 바꿀때 사용한다.
첫번째 파라미터는 Mutation 요청을 수행하기 위한 Promise를 return 하는 함수이며,
useMutation의 return 값 중 mutate(또는 mutateAsync)함수를 호출하여 서버에 부수효과를 발생시킬 수 있다.
function NotificationSwitch({ value }) {
// mutate 함수를 호출하여 mutationFn 실행
const { mutate, isLoading } = useMutation(
(value) => axios.post(URL, { value }), // mutationFn
);
return (
<Switch
checked={value}
disabled={isLoading}
onChange={(checked) => {
// mutationFn의 파라미터 'value'로 checked 값 전달
mutate(checked);
}}
/>
);
}
이때 update이후 get을 다시 시켜주려면 mutate 함수가 성공할 때 unique key로 맵핑된 get 함수를 invalidateQueries에 넣어주면 된다.
import axios from 'axios';
import { useMutation, useQueryClient } from 'react-query';
import { QUERY_KEY as todosQueryKey } from './useTodosQuery';
// useMutation에서 사용할 `서버에 Side Effect를 발생시키기 위해 사용할 함수`
// 이 함수의 파라미터로는 useMutation의 `mutate` 함수의 파라미터가 전달됩니다.
const fetcher = (contents: string) => axios.post('/todos', { contents });
const useTodosMutation = () => {
// mutation 성공 후 `useTodosQuery`로 관리되는 서버 상태를 다시 불러오기 위한
// Cache 초기화를 위해 사용될 queryClient 객체
const queryClient = useQueryClient();
return useMutation(fetcher, {
// mutate 요청이 성공한 후 queryClient.invalidateQueries 함수를 통해
// useTodosQuery에서 불러온 API Response의 Cache를 초기화
onSuccess: () => queryClient.invalidateQueries(todosQueryKey),
});
};
export default useTodosMutation;
'FE 이모저모 공부' 카테고리의 다른 글
에러) jest syntaxerror: cannot use import statement outside a module (0) | 2024.03.05 |
---|---|
React-Query를 이용한 병렬 데이터 관리 (0) | 2024.02.18 |
Testing-library를 이용해 React 컴포넌트 테스팅 (0) | 2024.01.12 |
Emotion 가볍게 공부하기 (0) | 2024.01.05 |
컴포넌트 UI 테스트를 위한 StoryBook (0) | 2024.01.02 |