Life is connecting the dots .

React Query - 서버 상태 관리 라이브러리 본문

Programming/React

React Query - 서버 상태 관리 라이브러리

soyeori 2023. 8. 11. 20:38

리액트 쿼리는 웹 어플리케이션에서 데이터 패칭, 캐싱뿐만 아니라 서버 상태의 데이터 동기화 및 업데이트를 손쉽게 해주는 상태 관리 라이브러리이다. 

 

리액트 쿼리 라이브러리 버전4에서는 TanStack Query로 이름이 변경되어 기존 리액트에서만 사용 가능했던 부분을 Solid, Vue, Svelte와 같은 다른 프레임워크에서도 사용이 가능하도록 업데이트되었다.

 

리액트 쿼리를 사용하면 다음과 같은 장점이 있는데, 무엇보다 손쉽게 사용할 수 있고 내장되어 있는 매소드를 통해 캐시와의 상호작용을 효율적으로 할 수 있는 점 때문에 많이 사용하는 라이브러리가 되었다.

 

✔️ 데이터 패칭, 자동 업데이트, 캐싱, 오류처리 등 최적으로 서버 상태 관리해 줌으로써 효율적으로 데이터를 다룰 수 있도록 해준다.

✔️ 별도 파일을 생성하거나 초기상태를 수정하는 것 없이 바로 사용가능하고(out-of-the-box, with zero-config), 복잡한 데이터로직을 간단하게 표현해서 사용할 수 있다.

✔️ 다양한 프레임워크 및 api와 함께 사용할 수 있다.

 

기본 사용법을 익히기 위해 TanStack Query 라이브러리를 사용하여 데이터 불러오기와 데이터 저장하기를 실습해 보았다. 사용방법은 Doc에 있는 내용을 참고하였다. Quick Start

라이브러리 설치

> npm i @tanstack/react-query

사용 방법

별도의 설정파일 없이 QueryClientProvider 컴포넌트로 App 컴포넌트를 감싸주었고, 캐시와의 상호작용을 위해 QueryClient를 생성하고 연결해 주었다.

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
...
const queryClient = new QueryClient();

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
  <React.StrictMode>
    ...
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    ...
  </React.StrictMode>,
);

Queries 

서버에서 데이터를 가져오기 위해 useQuery 훅을 호출하고, (1) 해당 쿼리에 대한 고유한 key(queryKey)와 (2) Promise를 반환하는 함수를 넣어 준다. JsonPlaceHolder에서 게시물들을 가지고 와서 화면에 보여주는 코드를 작성해 보았다.

import { useQuery } from '@tanstack/react-query';
...

interface Post {
  title: string;
  body: string;
}

const getPosts = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  const data = await res.json();

  if (!res.ok) {
    throw Error('데이터를 불러오는데 실패');
  }
  return data;
};

export function PostList() {
  ...
  const { data } = useQuery<Post[]>({ queryKey: ['posts'], queryFn: getPosts });

  return (
    <Container>
      <Header />
      {data?.map((post, index) => <BlogPost key={index} title={post.title} body={post.body} />)}
      ...
    </Container>
  );
}

Mutations

서버 데이터를 create/update/delete 하기 위해 useMutation 훅을 사용하여 mutation을 할 함수를 넣어주고, 사용할 곳에서 호출해 주었다. 뮤테이션이 진행되는 상태에 따라 isLoading, isError 등 옵션을 사용하여 상태에 따른 화면을 다르게 보여줄 수 있다.

  ...
  return (
    <Container>
      <Background />
      <Contents>
        {mutation.isLoading ? (
          '글 저장 중...🫥'
        ) : (
          <>
            {mutation.isError ? `일시적인 오류 발생: ${mutation.error}` : null}
            ...
          </>
        )}
      </Contents>
    </Container>
  );
}

 

또한 onError, onSuccess, onSettled 콜백함수를 사용해서 뮤테이션의 side effects를 수행할 수 있게 해 주는데,

useMutation 을 생성할 때뿐만 아니라 mutate 함수를 호출할 때 '추가' 콜백함수로 넣어줄 수 있다.

 

const mutation = useMutation({
  mutationFn: registerPost,
  onSuccess: () => alert('[first] 저장 완료!! 😎'),
});

mutation.mutate(post, {
  onSuccess: () => alert('[second] 저장 완료!! 🤩'),
});

 

 

특히 Side Effects와 관련된 옵션 중 onSuccess는 보통 Query client의 매소드와 함께 사용되어 뮤테이션 후의 상태를 업데이트하기 위해 활용할 수 있다. 또한 흥미롭게 본 부분은 콜백함수 중에서 onMutate 옵션으로, GraphQL 기반 Apollo Client 라이브러리에서 Optimistic UI를 구현했을 때와 같이 리액트 쿼리에서도 사용할 수 있다. (참고: Apollo Docs)

 

onMutate은 Optimistic Update를 지원할 수 있는 기능으로 뮤테이션이 실행되기 전에 동작한다. onMutate를 사용하면 낙관적으로 새로운 값을 직접 업데이트하되, 실패할 것을 대비해 roll back로직을 구현하고, 뮤테이션이 종료되면 invalidateQueries 메서드를 사용하여 업데이트 한 값으로 쿼리를 가져온다. 

다른 상태 관리 라이브러리와의 차이

리액트쿼리는 상태 관리 라이브러리로 소개되어 있는데 그렇다면 또 다른 상태 관리 라이브러리 redux와의 차이는 무엇일까? 우선, Docs에 의하면 다른 라이브러리가 클라이언트 상태 관리에 적합한 것에 비해 서버 상태 관리에는 적합하지 않다고 설명한다. 즉, 리액트쿼리는 '서버' 상태를 관리하기 위한 라이브러리인 점에서 큰 차이가 있다. 

While most traditional state management libraries are great for working with client state, they are 
not so great at working with async or server state.

 

즉, 쉽게 설명하자면 redux는 전역으로 클라이언트 데이터를 '저장'하고 다른 컴포넌트와 상태를 '공유'하는 데에, 리액트 쿼리는 '서버'에 저장된 데이터를 '처리'하는데 초점을 맞춘다. 이 작업을 비동기적으로 수행하며 API 응답 결과를 캐싱하여 서버와 클라이언트 간 기능을 처리한다. 그렇기 때문에 결과적으로 클라이언트상태와 서버 상태를 관리를 위해 두 개 라이브러리를 상호보완하여 함께 사용한다. 

 

또 다른 라이브러리로 GraphQL데이터를 쉽게 관리해 주는 Apollo Client는 데이터 패칭, 캐싱, 최적화 등 리액트 쿼리와 비슷한 기능을 수행하지만 graphql-api를 사용하는 프로젝트에 적합하고, 소규모 프로젝트에는 초기 셋팅에 대한 번거로움이 있을 수 있다. 반면에 리액트 쿼리는 api 종류에 제한 없이 사용가능하므로 더 효율적인 선택이 될 수 있다.

마무리

React-Query, Apollo-Client를 사용했을 때 두 라이브러리가 서버 상태 관리를 위해 비슷한 기능을 가진다는 생각이 들었고, 그렇기 때문에 리액트 쿼리도 빠르게 익히고 사용해 볼 수 있었다. 더불어, 리액트 생태계에서 리액트 쿼리가 주로 사용되는 점, rest-api뿐만 아니라 다양한 api를 다룰 수 있다는 점에서 리액트 쿼리 사용을 많이 고려해 볼 것 같다.

향후에는 리액트 쿼리 QueryClient에서 제공되는 매소드들 중 주요 매소드들을 활용하여 보다 효율적으로 사용하는 방법에 대해 깊이 알아볼 계획이다.

 


참고

React-Query Docs

What is React-Query

stackoverflow : main-difference-between-react-query-and-redux