[리액트] 리액트 쿼리 이해하고 사용해보기
이번 개인 프로젝트때 주로 사용했던 Tanstack Query에 대해 알아보자. 리액트는 정말 훅이 많다..고 항상 느낀다.. 이번 TIL은 리액트 쿼리에 대해 깊이는 아니지만 내가 이해한만큼 써보려고 한다.
Tanstack Query❓
💡 fetching, caching, 서버 데이터와의 동기화를 지원해주는 라이브러리
서버 상태를 관리하기 위한 라이브러리로, 데이터를 패칭하고 캐싱, 동기화, 무효화 등의 기능을 제공한다. 복잡하고 장황한 코드가 필요하지 않고 리액트 컴포넌트 내부에서 간단하고 직관적으로 API를 사용할 수 있으며 이전보다 비동기 로직을 간편하게 작성하고 유지보수성을 높일 수 있다.
사용하는 이유
비동기 로직의 복잡성 해결 필요성
기존의 방식은 Fetching 코드를 작성하고 데이터를 담아 둘 상태(useState) 생성, useEffect를 이용해 컴포넌트 마운트시 데이터를 Fetching 한 뒤 상태에 저장하였다. 리액트 쿼리를 사용하면 해당 로직의 복잡함을 해결할 수 있다.
서버 상태 관리의 어려움
서버 상태는 클라이언트 상태와 달리 캐싱, 동기화, 재검증 등 관리해야 할 요소가 많아 기존 방법으론 관리가 어려움.
주요기능
- 데이터 캐싱: DB서버에 여러번 동일한 데이터를 요청하지 않고 캐싱하여 데이터를 빠르게 가져온다.
- 자동 리페칭: 데이터가 변경되었을 때 자동으로 리페칭하여 최신 상태를 유지한다.
- 쿼리 무효화: 특정 이벤트가 발생했을 때 쿼리를 무효화하고 데이터를 다시 가져온다.
데이터를 가져오고, 수정하고, 리프레시한다. 무조건 외우기!!
📌 get / Modify / refresh
get = useQuery
Modify = useMutation
refresh = invalidateQueries
라이브러리 설치
yarn add @tanstack/react-query
리액트 쿼리를 사용하려면 백엔드 데이터 베이스가 필요해서 json-server로 임시 데이터 베이스를 만들어준다.
yarn add json-server
단축명령어 설정
📂package.json에서 아래와 같이 설정해준다. 그러면 yarn json 입력시 서버가 켜진다.
{
"scripts": {
"json": "json-server --watch db.json --port 5000",
}
}
단축 명령어 없이 서버를 키는 명령어는 아래와 같다.
json-server --watch db.json
db.json
📂src > 📄db.json
{
"expenses": [
{
"id": "f70630fd-852d-4566-a830-9d318c22046a",
"date": "2024-06-18",
"item": "📚",
"amount": 50000,
"desc": "리액트",
"month": 5,
"user": "wnswns"
},
]
}
QueryClient 생성
useQuery를 사용할 곳에 쿼리 클라이언트를 생성해준 뒤 Provider를 적용해준다. 이렇게하면 전역에서 사용할 수 있다.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById("root")).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
데이터 가져오기 : useQuery()
useQuery에 쿼리 키와 비동기 함수를 인자로 받아 데이터를 가져오고, 로딩 상태, 오류 상태, 그리고 데이터를 반환한다.
여기서 쿼리키는 배열로 들어간다.
useQuery({ queryKey: [쿼리키], queryFn: 비동기 함수 })
실무에서 자주 사용하는 쿼리키 이름
💡 꼭 알아야 하는 것
- 다른 API는 다른 쿼리키를 사용한다. (ex. 목록 API와 상세 API는 다른 쿼리키를 사용한다)
- 목록 페이지에선 `["posts"]` or `["movies"]` or `["todos"]`이렇게 쓰는 경우가 많음.
- 상세 페이지에서는 `["posts", id]` 이렇게 쓰는 경우가 많음.
- 페이지네이션의 쿼리키는 `["posts", page]` 이렇게 쓰는 경우가 많음
서버에서 데이터를 가져오는 API 함수를 만들어보자.
import axios from "axios";
const JSON_SERVER_HOST = "http://localhost:4000";
export const getExpense = async () => {
try {
const response = await axios.get(`${JSON_SERVER_HOST}/expenses`);
return response.data;
} catch (error) {
console.error(error);
alert("데이터를 가져오지 못했습니다.");
}
};
위에서 만든 API 함수를 useQuery에 적용해보자. 아래 코드로 적용하면 데이터 가져오는 로직 완성.
useQuery({ queryKey: ["expense"], queryFn: getExpense })
위 코드를 아래 코드처럼 쓸 수 있다.
- data : 화면에 보여줄 데이터 (queryFn의 return 값)
- isLoading : 데이터 로딩 여부 (true, false)
- error : 데이터 로딩 중 발생한 에러 데이터
const { data, isLoading, error } = useQuery({
queryKey: ["expense"],
queryFn: getExpense,
});
데이터를 받아오기 전 로딩중으로 뜨고, 데이터를 가져오지 못할 경우 예외 처리를 해줄 수 있다.
if (isLoading) {
return <div>로딩중입니다.</div>;
}
if (error) {
return <div>데이터를 불러오지 못했습니다.</div>;
}
데이터 추가, 수정, 삭제하기 : useMutation()
export const postExpense = async (newExes) => {
try {
const response = await axios.post(`${JSON_SERVER_HOST}/expenses`, newExes);
return response.data;
} catch (error) {
console.error(error);
alert("데이터를 생성하지 못했습니다.");
}
};
다른 컴포넌트에서 리액트 쿼리를 써야할 경우 ✔️ useQueryClient() 훅을 사용해줘야한다. invalidateQueries 속성을 사용해주기 위해선 필수이다. 생성 후 데이터를 다시 가져와야하기 때문에 invalidateQueries를 사용해줬다. 기존에 있던(데이터를 생성하기전) 데이터는 무효화시키고 최신 상태(데이터가 추가된)를 가져온다
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: postExpense,
onSuccess: () => {
queryClient.invalidateQueries(["expenses"]);
},
});
새로운 데이터가 추가되는 함수에 mutation.mutate 사용해주면 추가하는 로직도 완성이다. 차례대로 수정과 삭제도 같은 방법으로 하면 된다!
const onInsert = useCallback(
(date, item, amount, desc) => {
const newExes = {
id: uuidv4(),
date,
item,
amount,
desc,
month: selectedMonth,
user: userInfo.id,
};
mutation.mutate(newExes);
},
[mutation, userInfo]
);