React-Query를 사용해야하는 이유 - 2

이태관·2025년 1월 21일

Why-React-Query

목록 보기
2/2
post-thumbnail

이전 글에서는 데이터패칭을 중심으로 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 개발자라면 해결방법을 쉽게 떠올릴 수 있을겁니다.

  1. 부모 컴포넌트에서 데이터를 가져오고 props로 내려주는 방법
  2. Context API를 사용해 데이터를 전역 상태로 관리하기

아래와 같이 말이죠.


그러나 또 다른 문제

하지만 이 방법들 역시 새로운 문제를 발생시킵니다.

  1. Props Drilling
  • 부모 컴포넌트에서 props를 내려주는 방식은 컴포넌트 계층이 깊어질수록 관리가 어려워지고 코드가 복잡해집니다.
  1. Context API 성능 문제
  • Context Provider로 감싸진 컴포넌트는 상태가 변경될 때마다 모든 하위 컴포넌트가 리렌더링됩니다.

아래 링크는 Context API의 성능 문제를 설명한 글입니다.
Context API가 있는데 왜 상태 관리 라이브러리가 필요할까


근본적인 원인

단지 React에서 데이터를 가져오고 싶은데 생각해야할게 한두가지가 아닙니다.

이런 문제가 일어나는 이유는 비동기 상태동기 상태처럼 다루고 있기 때문입니다.


동기 상태 VS 비동기 상태

동기상태

  • 동기 상태란 브라우저에서 작업할 때 일반적으로 사용하는 상태를 의미합니다.
  • 필요한 순간 즉시 사용할 수 있으며, 다른 누군가가 조작할 수 없기 때문에 항상 최신 상태를 유지합니다.
  • 예를 들어 다크모드나 모달을 열고 닫을 때 생성하는 상태입니다.

클라이언트의 영향으로만 핸들링 되기 때문에 클라이언트 상태라고도 불립니다.

클라이언트 상태(Client State)의 특징

  • 클라이언트가 소유: 항상 최신 상태입니다.
  • 사용자만 변경 가능: 사용자만이 변경할 수 있습니다.
  • 일시적: 브라우저를 닫으면 사라집니다.
  • 동기적: 즉시 사용가능 합니다.

위 특징 때문에 클라이언트 상태는 예측 가능하며 다루기 쉽습니다.
사용자가 유일하게 상태를 변경할 수 있기 때문에, 잘못될 가능성이 거의 없습니다.

그렇기 때문에 클라이언트 상태는 useState useReducer Zustand Redux만으로 충분히 관리할 수 있습니다.

비동기 상태

  • 반면, 비동기 상태는 우리의 것이 아니고 어딘가(보통 서버)에서 가져와야하는 상태입니다.
  • 보통 DB에 저장되어 있으며, 즉각적으로 사용할 수 없습니다.
  • 시간이 지나면 이 상태를 관리하는 것은 까다로워집니다.

반대로 비동기 상태는 서버 상태라고 불립니다.

서버 상태(Server State)의 특징

  • 서버가 소유: 우리가 보는 상태는 서버에 요청했던 순간 받은 일종의 스냅샷입니다. 그렇기 때문에 최신 상태를 보장 할 수 없습니다.
  • 여러 사용자가 소유: 나 뿐만아니라 여러 사람이 데이터를 변경할 수 있습니다.
  • DB에 저장: 브라우저를 닫아도 데이터가 유지 됩니다.
  • 비동기적: 서버에서 클라이언트로 데이터가 전달되는 데 시간이 걸립니다.

React-Query의 역할

React Query는 데이터를 가져오는 것 자체가 아니라, 가져온 데이터를 효율적으로 관리하는 데 초점을 맞춘 라이브러리입니다. 서버 상태 관리에서 흔히 발생하는 문제들을 해결하기 위해 설계되었습니다.

React Query가 제공하는 주요 기능
1. 데이터 캐싱
2. 자동 리패칭
3. 로딩 및 에러 상태 관리
4. 중복 요청 방지

React Query를 도입하면 이러한 기능들을 통해 React의 복잡한 서버 상태 관리 문제를 간단하고 직관적으로 해결할 수 있습니다.


마무리

지금까지 살펴본 것처럼 중요한 것은 데이터를 가져오는것이 데이터를 가져온 이후, 시간이 지나면서 그 데이터를 어떻게 관리하느냐에 있습니다.

서버 상태는 클라이언트 상태와는 본질적으로 다르기 때문에, 기존의 상태 관리 도구만으로는 효율적으로 처리하기 어렵습니다.

우리가 서버 상태를 다루는 과정에서 겪게 되는 문제들, 예를들어

  1. 데이터를 최신 상태로 유지
  2. 동일한 데이터를 여러 컴포넌트에 효율적으로 공유
  3. 캐싱, 에러, 로딩, 리패칭 같은 작업도 처리해야하는 점들은 단순한 데이터 패칭 코드로 해결할 수 없는 영역입니다.

React-Query는 이런 문제를 해결하기 위한 라이브러리 입니다.

지금까지 React Query를 사용해야 하는 이유와 서버 상태 관리에서 발생하는 문제들에 대해 살펴봤습니다.

0개의 댓글