(탐구) React Query란?

한중일 개발자·2024년 2월 26일
0

React Libraries

목록 보기
2/3

리액트 쿼리?


Remote state를 관리하는 라이브러리다. 정식 이름은 Tanstack Query다.

주로 아래의 장점이 있다:

  • 캐시에 데이터 저장
  • 자동으로 로딩/에러 상태 관리
  • 상태 sync를 위한 자동 데이터 re-fetching
  • Pre-fetching

사용법

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // 쿼리가 최신 상태에서 더 이상 최신이 아닌 상태로 전환하는 시간
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      ...
    </QueryClientProvider>
  );
}   

위처럼 셋업한다.

useQuery

const {
    isLoading,
    data: cabins, // 받아온 데이터를 cabins에 저장
    error,
  } = useQuery({
    queryKey: ["cabins"],
    queryFn: getCabins, // 데이터를 api에서 받아오는 함수
  });

useQuery 훅이다. queryKey의 변화에 따라 쿼리가 업데이트된다. 로딩, 에러 상태를 쉽게 받아올 수 있다.

어떠한 Promise-based 메서드들과 사용이 가능하고, 서버의 데이터를 바꾸면 밑에서 얘기할 Mutation을 대신 사용한다.

쿼리에 사용하는 키는 내부적으로 refetching, caching, 쿼리 공유를 위해 사용된다.

상태

const result = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })

위의 result 객체는 많은 상태들을 가지고 있는데, 아래와 같은 상태들만을 가진다.

  • 데이터가 아직 없을때: isPending or status === 'pending'
  • 에러가 났을떄: isError or status === 'error'
  • 쿼리가 성공했고 데이터가 있을때: isSuccess or status === 'success'
const { status, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodoList,
  })

  if (status === 'pending') {
    return <span>Loading...</span>
  }

  if (status === 'error') {
    return <span>Error: {error.message}</span>
  }

data의 상태를 불리안이 아닌 형태로 알려주는 status 상태도 있다.

fetchStatus

status와 다르게, queryFn의 상태를 알려준다.
fetching, paused, idle이 있다.

쿼리 키

// An individual todo
useQuery({ queryKey: ['todo', 5], ... })

// An individual todo in a "preview" format
useQuery({ queryKey: ['todo', 5, { preview: true }], ...})

// A list of todos that are "done"
useQuery({ queryKey: ['todos', { type: 'done' }], ... })

위처럼 추가 id, 객체 등이 들어가 쿼리를 유일하게 표현하게 한다. 객체 내부의 키의 순서가 달라도 같은 객체로 치지만, array 내부의 아이템의 순서는 중요하다.

useMutation

const queryClient = useQueryClient();

const { isLoading: isDeleting, mutate } = useMutation({
  mutationFn: deleteCabin,
  onSuccess: () => {
    queryClient.invalidateQueries({
      queryKey: ["cabins"],
    });
  },
  onError: (err) => alert(err.message),
});

useQuery로 정의한 데이터를 update,delete해주는 훅이다.

받아온 데이터가 변경 성공시, 위 코드는 "cabins" 키로 캐시되어있는 데이터를 invalidate한다. 그러면 내부에서 데이터를 다시 fetch 해오기에 최신 데이터와 sync할 수 있다.

뒤에서 무슨 일이 일어나는 걸까?

사용법은 비교적 직관적이다. 이제 언제나 그렇듯 뒤에서 무슨 일이 일어나길래 이런 마법같은 상태관리가 가능한지 보도록 하자.

사진 출처: 링크
우선 App 루트에 적용한 QueryClient가 핵심이다. 이는 QueryCache와 MutationCache의 컨테이너고, 쿼리와 뮤테이션들의 기본 설정값들을 보관한다. 보통 캐시와 직접 상호작용 할 일은 없고, QueryClient를 통해서 한다.

QueryCache


사진 출처: 링크
QueryCache는 queryKey로 저장한 캐시들이 Query 클래스의 인스턴스인 형태로 저장되어있는, 인-메모리 객체다. 중요한건 캐시는 인메모리로 저장된다는 것이고, 이때문에 새로고침을 하면 캐시가 날아가는 것이다. localstorage같은 다른곳에 저장하고 싶으면 persister를 사용해야 한다.

사진 출처: 링크
쿼리들은 쿼리에 대한 정보 뿐만 아니라 쿼리 함수, 재시도 등 정보도 포함중이다. 쿼리는 변경된다면 해당 변경사항들에 Subscribe된 Observer들에게 알려진다. Context API가 생각나는 부분이다.

QueryObserver


사진 출처: 링크
쿼리들과 이를 사용하고 싶어하는 컴포넌트간 중간다리 역할을 하고있다. useQuery를 호출하면 만들어지고, 딱 한개의 쿼리에만 subcribe된다. 그래서 queryKey가 필요한 것이다!

Observer들은 컴포넌트가 쿼리의 어떤 prop들만 사용하는지 알기에, 지정된 필드 이외의 것들은 알려주지 않는다. data 필드만 사용한다면, isFetching이 바뀌었을때 컴포넌트가 재렌더링할 필요가 없다는 말이다.

이 Observer의 수는 devtool을 사용할때 쿼리 왼쪽에 있는 숫자로 알 수 있다.

컴포넌트의 관점에서 보기


사진 출처: 링크

  • 컴포넌트가 마운트되고, useQuery를 호출하여 Observer를 만든다.
  • Observer는 쿼리에 subscribe하고, 쿼리들은 QueryCache 내부에 존재한다.
  • Subscription은 쿼리가 없다면 생성하고, 데이터가 stale되면 백그라운드 re-fetch를 트리거한다.
  • Observer는 최적화를 진행하여, 업데이트가 있을때 컴포넌트가 알려주고, 컴포넌트는 새 상태를 렌더링한다.
  • Query의 실행이 끝나면, Observer는 이를 알게된다.

참고자료

Inside React Query
Inside React Query- 한국어

profile
한국에서 태어나고, 중국 베이징에서 대학을 졸업하여, 일본 도쿄에서 개발자로 일하고 있습니다. 유창한 한국어, 영어, 중국어, 일본어와 약간의 자바스크립트를 구사합니다.

0개의 댓글