
이전 글에서는 데이터패칭을 중심으로 React-Query를 사용하지 않았을 때 발생하는 문제를 다뤘습니다.
이전 글
이번에는 서버에서 받은 데이터를 React-Query를 사용하지않고 처리할 경우 발생하는 문제를 알아보겠습니다.
우선 이전 글에서 완성한 코드를 다시 보겠습니다.
useEffect(() => {
let ignore = false;
const handleFetchPokemon = async () => {
setPokemon(null);
setIsLoading(true);
setIsError(null);
try {
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
if (ignore) {
return;
}
if (res.ok === false) {
throw new Error(`Error fetching pokemon #${id}`);
}
const json = await res.json();
setPokemon(json);
setIsLoading(false);
} catch (e) {
setIsError(e.message);
setIsLoading(false);
}
};
handleFetchPokemon();
return () => {
ignore = true;
};
}, [id]);
서버에서 데이터를 가져와서 화면에 보여주려하는데 코드가 길어보이고 복잡해보입니다.
심지어 useEffect만 가져왔는데도 말이죠.
이 코드를 서버에서 데이터를 가져올 때마다 작성하게 되면 개발자는 큰 피로감을 느낄 것입니다.
그래서 대부분의 개발자는 추상화를 통해 데이터를 가져오는 custom hook을 작성하게 됩니다.
export default function useQuery(url) {
const [data, setData] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
let ignore = false;
const handleFetch = async () => {
setData(null);
setIsLoading(true);
setError(null);
try {
const res = await fetch(url);
if (ignore) {
return;
}
if (res.ok === false) {
throw new Error(`A network error occurred.`);
}
const json = await res.json();
setData(json);
setIsLoading(false);
} catch (e) {
setError(e.message);
setIsLoading(false);
}
};
handleFetch();
return () => {
ignore = true;
};
}, [url]);
return { data, isLoading, error };
}
이 코드로 모든 문제가 해결될까요? 안타깝게도 그렇지 않습니다.
이 방식은 데이터 중복 요청(duplicate) 문제를 야기합니다. 쉽게 이해하기 위해서 상황을 가정해보겠습니다.
헤더와 푸터에서 동일한 데이터를 사용해야 한다고 가정해봅시다.
겉으로는 문제가 없어 보이지만, 사실 같은 페이지에 위치한 컴포넌트에서 API 요청이 두 번 일어나게 됩니다.
이는 위 코드의 문제라고 볼 수는 없습니다. 근본적으로 React가 원래 그렇게 구현돼있기 때문이라고 보는게 맞을겁니다.
React에서 가져온 데이터는 데이터를 가져온 컴포넌트에만 존재하기 때문입니다.
단순히 API 요청이 두 번 발생하는 것은 큰 문제가 아닐 수 있습니다.
그러나 만약 헤더에서 API 응답 값이 A로, 푸터에서는 B로 응답되면 어떻게 될까요?
아래와 같이 동일한 API를 호출했음에도 불구하고, 컴포넌트별로 다른 값을 가지는 상황이 발생 할 수 있습니다.

사실 이 문제는 React 개발자라면 해결방법을 쉽게 떠올릴 수 있을겁니다.
Context API를 사용해 데이터를 전역 상태로 관리하기아래와 같이 말이죠.

하지만 이 방법들 역시 새로운 문제를 발생시킵니다.
Context Provider로 감싸진 컴포넌트는 상태가 변경될 때마다 모든 하위 컴포넌트가 리렌더링됩니다.아래 링크는 Context API의 성능 문제를 설명한 글입니다.
Context API가 있는데 왜 상태 관리 라이브러리가 필요할까
단지
React에서 데이터를 가져오고 싶은데 생각해야할게 한두가지가 아닙니다.이런 문제가 일어나는 이유는
비동기 상태를동기 상태처럼 다루고 있기 때문입니다.
클라이언트의 영향으로만 핸들링 되기 때문에 클라이언트 상태라고도 불립니다.
위 특징 때문에 클라이언트 상태는 예측 가능하며 다루기 쉽습니다.
사용자가 유일하게 상태를 변경할 수 있기 때문에, 잘못될 가능성이 거의 없습니다.
그렇기 때문에 클라이언트 상태는 useState useReducer Zustand Redux만으로 충분히 관리할 수 있습니다.
DB에 저장되어 있으며, 즉각적으로 사용할 수 없습니다.반대로 비동기 상태는 서버 상태라고 불립니다.
React Query는 데이터를 가져오는 것 자체가 아니라, 가져온 데이터를 효율적으로 관리하는 데 초점을 맞춘 라이브러리입니다. 서버 상태 관리에서 흔히 발생하는 문제들을 해결하기 위해 설계되었습니다.
React Query가 제공하는 주요 기능
1. 데이터 캐싱
2. 자동 리패칭
3. 로딩 및 에러 상태 관리
4. 중복 요청 방지
React Query를 도입하면 이러한 기능들을 통해 React의 복잡한 서버 상태 관리 문제를 간단하고 직관적으로 해결할 수 있습니다.
지금까지 살펴본 것처럼 중요한 것은 데이터를 가져오는것이 데이터를 가져온 이후, 시간이 지나면서 그 데이터를 어떻게 관리하느냐에 있습니다.
서버 상태는 클라이언트 상태와는 본질적으로 다르기 때문에, 기존의 상태 관리 도구만으로는 효율적으로 처리하기 어렵습니다.
우리가 서버 상태를 다루는 과정에서 겪게 되는 문제들, 예를들어
React-Query는 이런 문제를 해결하기 위한 라이브러리 입니다.
지금까지 React Query를 사용해야 하는 이유와 서버 상태 관리에서 발생하는 문제들에 대해 살펴봤습니다.