import React, { useState, useEffect } from "react";
import axios from "axios";
const App = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get("http://localhost:4000/todos");
setData(response.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Fetched Data</h1>
<ul>
{data &&
data.map((item) => (
<li key={item.id}>
<h2>{item.title}</h2>
<p>{item.isDone ? "Done" : "Not Done"}</p>
</li>
))}
</ul>
</div>
);
};
export default App;
적절한 UI를 보여주지만 다음과 같은 문제가 존재한다.
비즈니스 로직이란?
애플리케이션의 핵심 동작을 정의하는 코드
서버 상태 vs 클라이언트 상태란?
클라이언트 상태 : UI와 관련된 일시적인 데이터
서버 상태 : 서버에서 가져오는 데이터 (API 응답), 캐싱, 동기화, 등의 관리 필요
action
이 reducer
에 도달하기 전에 중간에서 가로채서 추가적인 작업을 수행할 수 있게 해주는 함수dispatch
가 일어난다.dispatch
가 일어날 때 action 객체를 같이 dispatch
한다.UI 컴포넌트에서 dispatch
가 일어난다.
dispatch
가 일어날 때 thunk 함수를 dispatch
한다.
액션 객체가 아니라, 함수를 dispatch한다는 것이 핵심
함수 안에서 여러가지 비동기 로직 처리 가능
전달된 Thunk 함수에 의해 Redux Thunk 미들웨어가 이 함수를 호출하고, 함수 안에서 비동기 작업을 수행한다.
비동기 로직이 수행된 이후 필요에 따라 액션 객체를 별도 생성하여 dispatch
한다.
dispatch
된 액션 객체는 리듀서로 전달되어 Redux store를 업데이트 한다.
Redux Thunk를 사용하면 비동기 로직이 액션 크리에이터에 포함되는데, 이를 테스트하기 위해서는 다양한 응답 상태(로딩, 성공, 실패)를 시뮬레이션해야 합니다. 이러한 비동기 작업을 모킹(mocking)하고, 상태 변화를 검증하는 테스트 코드를 작성하는 과정이 복잡해집니다. 또한, 비동기 로직과 관련된 여러 상태를 관리해야 하므로 테스트 코드의 양도 많아집니다.
TanStack Query : 서버 상태 관리 라이브러리
Redux Thunk와 같은 문제들을 해결하기 위해 TanStack Query가 등장
TanStack Query는 서버 상태 관리에 특화 된 기능을 제공하여 복잡한 비동기 로직을 단순화 하고 캐싱, 동기화, 리페칭 등의 기능을 쉽게 구현할 수 있다.
npm i @tanstack/react-query
QueryClientProvider
// main.jsx or index.js
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
useMutation
invalidateQueries
useQuery
1 queryKey
queryFn
useMutation
mutationFn
: 추가, 수정,삭제와 관련된 비동기 로직이 들어간다. isSuccess
, isError
등을 이용해 상태에 따른 관리를 할 수 있다.invalidateQueries
useMutation
과 함께 사용SWR : 최신 데이터가 도착하기 전까지 기존 캐시 데이터를 사용하는 전략
그림 순서를 잘 볼 것
상태 | 설명 |
---|---|
fresh | 데이터를 새로 패칭할 필요 없는 상태 staleTime 이 지나지 않은 상태로, 캐시 데이터를 그대로 사용가능 |
stale | 데이터를 새로 패칭해야 하는 상태. staleTime 이 지난 후로, 새로운 데이터를 가져오기 위해 쿼리가 실행됨 |
active | 현재 컴포넌트에서 사용 중인 쿼리 상태. 컴포넌트가 마운트되어 쿼리를 사용하고 있을 때 |
inactive | 더 이상 사용되지 않는 쿼리 상태. 컴포넌트가 언마운트되거나 쿼리가 더 이상 필요하지 않을 때 |
deleted | 캐시에서 제거된 쿼리 상태입니다. gcTime 이 지나면 쿼리가 캐시에서 삭제되어 이 상태가 됨 |
fetching | 데이터를 서버에서 가져오고 있는 상태. 이 상태에서는 isFetching이 true 로 설정됩니다. |
gc : garbage collection
staleTime: 0
refetchOnMount: true
refetchOnWindowFocus: true
refetchOnReconnect: true
gcTime : 1000 * 60 * 5 (5분)
rety: 3
select
select
: 쿼리 함수에서 반환된 데이터의 특정 부분만 가져오거나, 데이터를 변환하는 작업을 할 수 있다. 단, 캐시 데이터는 원본 데이터를 유지한다.
import { useQuery } from 'react-query'
function User() {
const { data } = useQuery({
queryKey: ["user"],
queryFn: fetchUser,
select: user => user.username
});
return <div>Username: {data}</div>
}