React
- 이전에 짧게나마 리액트 쿼리를 정리한 글이 있다.
- 급하게 사용해야 했기에 겉핥기 식으로 나마 사용했었고, 데이터 캐싱이 필요해져 쿼리를 쓸 상황이 왔고 다시 찾아보게 되었다.
- 저번에 사용할 때도 급하게 배운만큼 깊이 있게 배우지 못했다는 생각이 들어 커스텀훅과 리액트 쿼리의 여러 옵션들을 찾아보다, 여태 블로그 글만 들여다봤던 것과는 달리 공식 문서를 들여다 보면 좋겠다는 생각이 들어 살펴보게 되었다.
- 확실히 공식 문서에 더 많은 내용이, 더 자세히 나와있었고, 사용법과 상황 예시 등 쿼리에 대해서 더 깊이 있게 알아볼 수 있는 기회였다.
- 나중에 다시금 볼 상황이 온다면, 또 블로그를 찾아가며, 공식 문서를 들여다보지 않기 위해 정리를 해보려한다.
React-Query
- 다시 짚는 쿼리란, 리액트 쿼리는 서버와의 데이터 동기화를 지원해주는 라이브러리라고 소개한다.
- 리액트 쿼리를 사용하기 전에 리덕스를 사용하고 있었는데, 리덕스만 예시로 들어보아도, 비동기 데이터를 처리하기 위한
redux-saga, toolkit
등의 다른 라이브러리가 추가적으로 필요하고, 또한 사용해보진 못 했지만 기본 코드 구조도 매우 복잡했다. - 이러한 단점들을 보완한 리액트 쿼리는 데이터 캐싱, 심플한 구조, 별도의 가공 없이 사용할 수 있는 기능 등의 매우 큰 장점을 갖고 있어 주로 사용된다.
공식 문서
- 내용이 너무 많지만 최대한 작성해보기 😅
설치 및 기본 세팅
npm install react-query
를 통해 설치한다.- 리덕스를 사용할 때처럼, 전역적으로 사용할 수 있도록 최상위 컴포넌트를 감싸준다.
- 리액트 최신 버전인 18버전과 타입스크립트를 함께 사용하는 중, 리액트 쿼리를 사용하려고 한다면 에러가 발생할 수 있다.
- 호환성 문제의 해결책으로
react-query
가 아닌tanstack/react-query
를 설치하여 사용한다. - 리액트 쿼리의 버전 중 v3가 가장 안정적이고, v4, v5는 실험용 버전이다.
- 따라서 공식 문서에서도 기본 소개를 v3로 하고 있는데, v4에서는 패키지 이름부터
tanstack/react-query
로 변경되었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
npm install react-query
import { QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
function App(){
return (
<QueryClientProvider client={queryClient}>
<App/>
</QueryClientProvider>
)
};
기본 구조
- 최상위 컴포넌트에서
queryClient
를 넘겨줬다면 이제 사용법을 알아보자 - 리액트 쿼리는 여러가지 기능이 있지만 대표적인 기능들은 아래와 같다.
- 데이터를 받아온다.(GET) => useQuery
- 데이터를 변경한다.(PUT, UPDATE, DELETE) => useMutation
- 여러 개의 쿼리를 실행하겠다.(Promise all) => useQueries
++
- 추가적으로 리액트 쿼리의 세 번째 인자로 다양한 값을 지정할 수 있고, 여기에 여러 가지 옵션들을 설정할 수 있다.
useQuery
- 첫 번째 인자로 유일한
key
값이 들어간다.- 리액트 쿼리는
key
를 갖고 데이터를 식별한다. - ex. 이번에 요청할
data
의key = user
, =>user
로 사용했던 데이터를 찾는다, 식별한다.
- 리액트 쿼리는
- 두 번째 인자로 실행할 함수를 작성한다.
- 비동기 함수가 들어가며
Promise
를 반환하는 형태여야 한다.
- 비동기 함수가 들어가며
1
2
3
4
const { data, isLoading, error } = useQuery({
queryKey: ["repoData"],
queryFn: () => fetch("api 주소").then((res) => res.json()),
});
useQuery의 동기적 실행
- 기본적으로 비동기 함수이지만, 옵션을 통해 동기적으로 실행할 수 있다.
- useQuery의 세 번째 인자로 다양한 옵션 값들이 들어가는데, 여기서 enabled에 값을 대입하면 해당 값이 true일 때 useQuery를 동기적으로 실행한다.
1
2
3
4
5
6
7
8
const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList);
const {
data: nextTodo,
error,
isFetching,
} = useQuery("nextTodos", fetchNextTodoList, {
enabled: !!todoList, // true가 되면 fetchNextTodoList를 실행한다
});
keepPreviousData
- 오래된 데이터가 존재한다면 데이터를 불러오기 전까지 오래된 데이터를 유지하는 기능을 한다.
- 데이터를
refetch
하는 과정에서는 대게 로딩 스피너를 표시하는데, 로딩 스피너는 사용자들에게 느린 데이터 속도로 인식되기 때문에, 로딩 스피너보다 기존에 존재하던 데이터를 보여줌과 동시에 백그라운드에서는refetch
를 수행하여 해당 데이터를 다시 검증한다.
1
2
3
4
useQuery([queryKey], DataFn, {
keepPreviousData: true,
...
})
useQueries
- 여러 개의
useQuery
를 실행할 때, 사용한다.
1
2
3
4
5
6
const results = useQueries({
queries: [
{ queryKey: ["post", 1], queryFn: fetchPost, staleTime: Infinity },
{ queryKey: ["post", 2], queryFn: fetchPost, staleTime: Infinity },
],
});
useMutation
PUT, UPDATE, DELETE
를 실행할 때 사용된다.- 반환 값은
useQuery
와 동일하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const App = () => {
const mutation = useMutation({
mutationFn: (newTodo) => {
return axios.post("/todos", newTodo);
},
});
return (
<div>
{mutation.isLoading ? (
"Adding todo..."
) : (
<React.Fragment>
{mutation.isError ? (
<div>occured error : {mutation.error.message}</div>
) : null}
{mutation.isSuccess ? <div>Todo added</div> : null}
<button
onClick={() => {
mutation.mutate({ id: new Date(), title: "..." });
}}
>
Create Todo
</button>
</React.Fragment>
)}
</div>
);
};
useMutation
의mutate
함수는Promise
를 반환하지 않기 때문에 직접적으로 할당하여 사용할 수 없지만,mutateAsync
함수는Promise
를 반환하기 때문에try...catch
문에서 호출하여 성공과 실패와 같은 서버의 응답처리를 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { mutate, mutateAsync } = useMutation(...);
// X
const result = await mutate()
// O
mutate(api, {
onSuccess: data => {
console.log(data)
},
onError: error => {
console.error(error)
},
onSettled: () => {
console.log('settled')
},
})
// O
try{
const result = await mutateAsync(api);
}catch(err){
...err
}
기본값 default
refetch 데이터 타시 받기 : 쿼리는 특정 이벤트, 시간의 조건 등 여러 조건에 의해 데이터를 다시 받아오는 동작을 한다.
refetchOnMount
: 기본값 true, 마운트 되었을 때 데이터를 받아온다.refetchOnWindowFocus
: 기본값 true, 윈도우가 포커싱 되었을 때(윈도우를 클릭하거나, Alt + tab으로 다시 윈도우를 여는 등) 데이터를 받아온다.refetchOnReconnect
: 기본값 true, 네트워크 재연결이 발생했을 때 데이터를 받아온다.
시간에 따른 데이터
staleTime
: 기본값 0, 사전적 정의로 [오래된, 신선하지 않은] 이라는 뜻을 가지고 있다. 데이터는fresh, stale
이라는 두 가지 상태를 갖고 있는데,stale
한 상태일 경우, 위 3가지 경우에서 데이터를 다시 요청한다.(기본값 0 === 데이터를 받자마자 오래된 데이터라 정의한다.)cacheTime
: 기본값 5 _ 60 _ 1000(5분), 데이터가 남아있는 상태를 말하는데, 설정 시간이 다 지나기 전에 데이터를 동일한 데이터를 다시 받아와야 할 때(언마운트) 데이터를 받아오는 동안 캐시된 데이터를 보여준다.
- 위의 옵션들로 인해, 리액트 쿼리를 연결하여 기본적인
console
코드를 추가했을 때, 콘솔에 메세지가 반복되어 뜨는 것을 확인할 수 있다. - 이러한 옵션 값들은 쿼리 문의 기본값을 수정할 수 있다.
1
2
3
4
5
6
7
8
9
10
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnMount: false,
refetchOnWindowFocus: false,
staleTime: 30000,
cacheTime: 30000,
},
},
});
useErrorBoundary
는 리액트 16 이상에서 제공하는Fallback UI
설정이다.
1
2
3
4
5
6
7
8
9
10
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 0,
},
mutations: {
useErrorBoundary: true,
},
},
});
useErrorBoundary
는onError
값에 따라 필요하지 않을 수도 있기 때문에onError
여부에 따라 옵션을 설정할 수도 있다.
1
2
3
4
5
6
7
8
9
10
import { useQuery as useQueryOrigin } from "react-query";
export default function useQuery(queryKey, queryFn, options = {}) {
const { onError } = options;
return useQueryOrigin(queryKey, queryFn, {
...options,
useErrorBoundary: !onError,
});
}