공부혜옹
[React-Query] React에서 ReactQuery로 Github user 정보 불러오기 본문
사용 이유
비대한 store의 크기를 예방하고, store에서 비동기 코드와 client state 코드를 분리하고 싶었다. 무엇보다 캐시 이용과 호출 상태에 대한 기능을 제공한다고 해서 꼭 경험해보고싶었다!
이번에 기술공부를 위한 토이프로젝트에서 github API를 사용하여 user정보를 불러올 일이 있었다.
본래는 redux-saga를 사용해 user정보를 호출하려고 하였으나 이전 사용 경험에서 api 하나당 많은 보일러 플레이트 코드들이 필요했고 복잡하여 유지보수가 쉽지 않았던 경험이 있었다.
그래서 이러한 이슈를 해결하고자 React Query를 공부하여 적용해보기로 하였다.
React Query 개념
React Query는 React 애플리케이션에서 서버 상태 가져오기, 캐싱, 동기화 및 업데이트를 쉽게 만드는 라이브러리이다.
React Query 의 상태
- fetching: 요청중인 쿼리
- fresh: 만료되지 않은 쿼리. 컴포넌트가 마운트, 업데이트 되어도 데이터를 새로 요청하지 않는다.
- stale: 만료된 쿼리. 컴포넌트가 마운트, 업데이트 되면 데이터를 새로 요청한다.
- 새로운 쿼리 인스턴스가 마운트되었을 때
- 브라우저 윈도우가 다시 포커스되었을 때
- 네트워크가 다시 연결되었을 때
- refetchInterval 옵션이 있을 때
- inactive: 사용하지 않는 쿼리. 일정시간(5min)이 지나면 GC(가비지 컬렉터)가 캐시에서 제거한다.
- delete: 가비지 컬렉터에서 제거된 쿼리
React Query의 LifeCycle
User라는 이름의 쿼리를 예시로 설명하도록 하겠다
- User 쿼리의 인스턴스가 mount된다
- 호출을 통해 데이터를 fetch하고 User라는 유니크한 QueryKey로 캐싱한다
- 갓 호출된 데이터는 fresh(기간 미만료) 상태이나 staleTime(기본값 0s)이 지나면 stale(기간만료)로 전환된다
- User 쿼리 인스턴스가 unmount되어 inactive상태가 된다
- inactive상태에서 cacheTime(기본값 5min)이 지나면 GC가 캐시에서 제거한다
- cacheTime이 지나기전 다시 쿼리 인스턴스가 mount되면 fetch가 진행되고, fresh한 데이터를 다시 가져오는 동안 캐싱된 데이터를 보여준다
⁉️ 갓 호출된 데이터가 0s가 지나 stale이 되면 fresh 구간이 없는거나 마찬가지 아닌가요?
맞다. 사실상 useQuery, useInfiniteQuery로 데이터를 가져올 경우 따로 설정해주지 않는 이상 계속 해서 refetch되는 구조이다.
Infinity로 설정하면 쿼리 데이터는 직접 캐시를 무효화할 때까지 fresh 상태로 유지된다.
useQuery 개념
useQuery(queryKey, fetch함수, optional)// 필수 인자는 queryKey와 fetch함수
QueryKey란?(필수)
첫번째 인자 queryKey는 유니크한 키값으로 queryKey가 캐싱되어 있는지를 확인하여 같은 쿼리를 사용한다.
Key는 hierarchy 구조이기 때문에, 이를 활용해서 분리하는 용도로 키를 설정할 수 있다.
예를 들어,
A : [‘A’, ‘product’, ‘10’]
B : [‘A’, ‘product’, ‘20’]
C : [‘A’, ‘user’, ‘30’]
쿼리 키를 위와 같이 설정하고, A, B, C 쿼리를 모두 갱신하고 싶은 경우에는 invalidateQueries([‘A’])로 갱신이 가능하고, product에 사용하는 A, B 쿼리만 갱신하고 싶은 경우에는 invalidateQueries([‘A, ‘product’])로 갱신이 가능하다.
배열을 사용한 Query Key는 배열 요소의 순서가 다르면 다르게 해싱된다. 객체 key는 순서 상관이 없다
useQuery(["A", "B"],{C, D}) //1
useQuery(["B", "A"],{C, D}) //2
useQuery(["A", "B"],{D, C}) //3
1번과 2번 Key는 다르다. 하지만 1번과 3번은 같은 Key로 인식된다
fetch함수란?(필수)
두번째 인자인 fetch함수는 Promise함수이다.
optional?(선택)
- enabled : true로 설정시에만 쿼리 요청이 실행된다. 이걸 이용해서 나열된 query들이 병렬실행이 아닌 동기적 실행을 하도록 할 수 있다
- placeholderData : mock 데이터 설정. 하지만 캐싱이 안된다
- initialData : 초기값 설정
굉장히 많아서 나머지는 링크로 대체한다
useQuery | TanStack Query Docs
return 값
- data: 데이터값
다음은 상태값으로 앞에 is를 붙이면 판별가능하다.
- idle : 쿼리 data가 비었을 때.
{enabled : false}
상태로 쿼리가 호출되었을 때 이 상태로 시작된다. - loading : 로딩중
- error : 에러 발생
- success : 요청 성공
useQuery 특징
- 비동기로 실행된다 useQuery를 여러개 실행할 경우 순차 실행이 아닌 병렬적으로 실행된다
- enabled option을 세번째 인자로 넘기면 동기실행이 가능하다
- **Dynamic Parallel Queries일 경우 hook 규칙을 위반하기 때문에 useQueries를 사용해야한다
Github API 호출하기
provider 전역 설정
React Query는 캐시를 관리하기 위해 QueryClient
인스턴스를 사용한다. 내부적으로 context를 사용하기 때문에 provider가 필요하다
import {
QueryClient,
QueryClientProvider,
} from "react-query";
import Profile from "./Profile";
const queryClient = new QueryClient();
function App() {
return (
// 애플리케이션에 클라이언트를 제공한다. provider안에 선언된 내용은 queryclient에 접근이 가능하다
<QueryClientProvider client={queryClient}>
<Profile />
</QueryClientProvider>
);
}
export default App;
useQuery 사용
useQuery를 컴포넌트 내에서 사용하지 않고 따로 query 관련 커스텀훅을 만들었다.
import {useQuery} from. "react-query"
const API_URL = "https://api.github.com/users/";
//데이터의 타입을 any로 둔것에 대해 심심한 사과의 말씀을....
const fetcher = (userID: string) =>
axios.get<any>(API_URL + userID).then(({ data }) => data);
const QUERY_KEY = ["userInfo"];
const useUserQuery = (userID: string) => {
return useQuery(QUERY_KEY, () => fetcher(userID), {
onSuccess: (data) => {
console.log(`success:${data}`);
},
});
};
import React from "react";
import { useQuery, useQueryClient } from "react-query";
import useUserQuery from "./quries/useUserQuery";
const Profile = () => {
// useUserQuery (데이터 패칭하기 위한 hook)
const { data, status } = useUserQuery("userName"); //github userName을 입력하면됩니다
if (status === isFetching) {
console.log('fetching...');
}
if (status === isLoading) {
console.log('loading...');
}
if (status === isError) {
console.log('error', error);
}
if (status === isSuccess) {
console.log('success', data);
}
return (
<div>
<ul>
{/* useQuery로 가져온 데이터는 아래와 같이 query.data로 꺼내올 수 있다. */}
<li>{data?.login}</li>
</ul>
</div>
);
};
export default Profile;
결과값.jpg
내가 느낀 장점과 고민점
장점
- store에서 비동기 관련 코드를 분리할 수 있다.
- store 크기를 줄일 수 있다.
- API 호출 상태에 대한 인터페이스 구현이나 처리를 따로 해주지 않아도 기능을 제공한다
- 데이터 캐싱!
고민점
- 프로젝트 크기가 커질 수록 어디서 어떤 Query가 쓰이고 있는지 파악이 힘들 것 같다.( 설계를 정말 촘촘히 신경써서 해야할 것 같다 )
사실상 Key를 사용해 캐싱된 데이터를 전역적으로 사용하는게 가능한 컨셉이다 보니 Key관리를 따로 해주어야하는게 아닌가? 하는 생각을 했었지만 따로 query에 대한 커스텀 훅을 만들어 쓰면 굳이 관리가 필요 없을 것 같다 이부분은 공부를 더 해보는 걸로!
다음에 해볼것
- QueryKey를 어떻게 효과적으로 관리할 수 있을지 탐구해볼것
- test용 코드라 크게 설계를 하지않고 커스텀 훅을 만들었지만 실제 프로젝트에선 어떤식으로 관리하고 사용할지 고민해볼것
- 현재는 react query 사용법을 익히기 위해 단순히 유저네임을 집어넣어 해당 유저의 정보를 가져왔지만 다음엔 github login을 통해 토큰을 발급받아 user 정보를 불러오는 것을 개발해볼것
- mutation 관련해 공부했던것들도 마저 포스팅하기!
'공부합시다 > React' 카테고리의 다른 글
jest SyntaxError: Cannot use import statement outside a module (0) | 2022.12.05 |
---|---|
[React-Query] Query Key 관리하기 (1) | 2022.11.28 |
NextJS 사전렌더링 2. getStaticPaths를 이용한 동적페이지 사전렌더링 (0) | 2022.10.24 |
NextJS 사전렌더링 1. SSG와 getStaticProps (0) | 2022.10.20 |
NextJS 파일기반 라우팅 (0) | 2022.10.17 |