관리 메뉴

거니의 velog

(49) 리액트 쿼리와 리코일 1 본문

SpringBoot_React 풀스택 프로젝트

(49) 리액트 쿼리와 리코일 1

Unlimited00 2024. 3. 11. 16:41

* 리액트의 인기 요인 중 하나는 방대한 생태계에 있다. 리액트의 버전이 변경되면서 추가되는 기능보다 빠른 속도로 라이브러리나 프레임워크들이 등장하고 있는 상황이다.
* 이번 장에서는 비동기 처리를 더욱 손쉽게 할 수 있는 리액트 쿼리(React Query)와 리덕스 대신에 많이 사용하는 상태관리 라이브러리인 리코일(Recoil)을 이용한다. 리액트 쿼리는 기존 서버의 데이터를 캐싱하는 기능을 가지고 있어서 불필요한 서버 호출을 줄여줄 뿐 아니라 간략한 코드로 기능을 구성할 수 있습니다. 리코일은 과거 리덕스(리덕스 툴킷 이전)의 복잡함을 없애고 손쉽게 애플리케이션의 상태 관리를 가능하게 한다.
* 이번 장의 학습 목표는 다음과 같다.

1. 리액트 쿼리(React Query)의 설정과 상품 관리 적용

2. 리코일(Recoil)의 설정과 로그인 처리 적용

1. 리액트 쿼리

* 리액트 애플리케이션 내부의 데이터를 구분하자면 애플리케이션 내부에서 유지되는 데이터와 API 서버에서 발생하는 외부 데이터로 구분할 수 있다. React Query(이하 리액트 쿼리)는 이 중에서 주로 외부에서 만들어진 데이터를 관리하는데 도움이 되는 라이브러리이다.
* 리액트 쿼리의 가장 중요한 특징은 데이터를 일정 시간 보관하는 캐싱 기능이다. 단순히 데이터를 보관하는 기능이 아니라. 단순히 데이터를 보관하는 기능이 아니라, 일정 시간이 지나면 새로 서버를 호출하도록 구성할 수도 있다. 이를 이용하면 프로젝트에서 장바구니에 담긴 상품이 갱신되었을 때 사용자의 장바구니가 일정 시간 후에는 자동으로 갱신되도록 설정할 수 있게 된다.
* 리액트 쿼리가 일정 시간 동안 데이터를 보관하는 기능(캐싱)을 제공하기 때문에 빈번하게 변경되지 않는 데이터에 대해서 서버 호출을 줄여주는 효과를 기대할 수 있다. SNS와 같이 실시간에 가까운 데이터가 아니라면 매번 서버를 호출해야 할 필요가 없기 때문에 리액트 쿼리는 이러한 상황에서 도움이 된다. 예제에서는 이전에 개발했던 상품과 사용자의 장바구니에 리액트 쿼리를 적용해 본다.


(1) 리액트 쿼리의 설정

* 리액트 쿼리는 3, 4, 5 버전의 사용이 가능한데 예제에서는 4 버전을 이용해서 코드를 작성한다.
* 아래의 주소를 참고한다. 버전에 따라서 deprecated 된 기능들이 있으므로 주의해서 사용해야 한다.
https://tanstack.com/query/v4/docs/framework/react/overview

Overview | TanStack Query Docs

tanstack.com

* 4 버전의 경우 아래 CLI를 통해서 프로젝트에 추가할 수 있다.

$ npm i @tanstack/react-query

// 4.35.7 버전으로 지정하여 설치 시
$ npm install @tanstack/react-query@4.35.7

* 리액트 쿼리 개발 시에는 개발도구를 같이 설치해서 사용하는 것이 편리하므로 아래 CLI를 통해 추가한다. 추가된 라이브러리는 package.jsom을 통해서 확인한다.

$ npm i @tanstack/react-query-devtools

// 버전에 맞게 설치
$ npm install @tanstack/react-query-devtools@4.35.7

* 리액트 쿼리의 기본적인 설정은 애플리케이션 내에서 리액트 쿼리의 QueryClient를 지정하는 것으로 시작한다. 프로젝트 내에 있는 App.js를 이용해서 설정한다.

import React from "react";
import { RouterProvider } from "react-router-dom";
import root from "./router/root";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const queryClient = new QueryClient();

const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={root} />
      <ReactQueryDevtools initialIsOpen={true} />
    </QueryClientProvider>
  );
};

export default App;

* 개발도구와 관련해서 initialsOpen 속성은 말 그대로 애플리케이션 구동 시에 개발도구를 오픈한 상태에서 시작하는 것을 지정한다(브라우저 개발자 도구의 application 메뉴에서 localStorage 항목으로 지정된 값을 이용하므로 나중에 이를 조정할 수 있다). 애플리케이션을 실행하면 아래 같은 화면이 보이고 왼쪽 하단의 close 메뉴를 통해 아이콘만 보이게 된다.


(2) useQuery()를 이용한 상품 조회

* 리액트 쿼리의 사용법 중에서 가장 먼저 useQuery()를 알아보자. useQuery()는 특정 데이터를 조회하고 통신 상태나 결과 혹은 에러 데이터 등을 한 번에 처리할 수 있게 도와준다. useQuery()를 이용하기 전에는 useEffect()를 이용해서 서버에서 데이터를 가져온 후에 상태(state)를 변경해 주어야만 했다. 이 과정에서 통신 중이거나 통신 완료된 상태를 표현하는 일을 리액트 쿼리로 변환해 본다.
* 상품 조회 기능에서 useQuery()를 이용해 본다.

* useQuery()의 파라미터는 크게 '쿼리 키(key), 쿼리 함수, 옵션'으로 구분된다. 이 중에서 '쿼리 키(key)'는 리액트 쿼리에서 식별자처럼 사용된다.
* 상품 조회에서 장바구니 부분은 조금 뒤쪽에서 다루기로 하고 상품을 조회해서 출력하는 부분만 다음과 같이 작성해 본다(기존의 코드와 비교해 보면 상당히 많은 양의 코드가 줄어드는 것을 확인할 수 있다).

import React from "react";
import { getOne } from "../../api/productsApi";
import { API_SERVER_HOST } from "../../api/todoApi";
import useCustomMove from "../../hooks/useCustomMove";
import FetchingModal from "../common/FetchingModal";
import useCustomCart from "../../hooks/useCustomCart";
import useCustomLogin from "../../hooks/useCustomLogin";
import { useQuery } from "@tanstack/react-query";

const initState = {
  pno: 0,
  pname: "",
  pdesc: "",
  price: 0,
  uploadFileNames: [],
};

const host = API_SERVER_HOST;

const ReadComponent = ({ pno }) => {
  //화면 이동용 함수
  const { moveToList, moveToModify } = useCustomMove();
  const { isFetching, data } = useQuery(["products", pno], () => getOne(pno), {
    staleTime: 1000 * 10,
    retry: 1,
  });

  const handleClickAddCart = () => {};

  const product = data || initState;

  return (
    <div className="border-2 border-sky-200 mt-10 m-2 p-4">
      {isFetching ? <FetchingModal /> : <></>}

      (...)
    </div>
  );
};

export default ReadComponent;

* useQuery()의 결과로는 서버 호출의 상태나 데이터 등을 반환하는데 개발자들은 이 중에서 필요한 결과 속성들을 이용하게 된다.
https://tanstack.com/query/v4/docs/framework/react/reference/useQuery

useQuery | TanStack Query Docs

tanstack.com

* 예제에서는 isFetching을 이용하는데 isFetching은 서버와 비동기 통신 중인지를 확인할 때 사용할 수 있다. data는 서버에서 처리된 결과 데이터를 의미한다. 이 외에도 error나 isError, isSuccess와 같이 다양한 결과를 활용할 수 있다.
* isFetching을 이용하면 별도의 useState() 없이 FetchingModal을 보여줄 수 있다.


[staleTime과 쿼리 키(key)]

* 리액트 쿼리의 가장 중요한 점은 데이터를 보관한다는 것이다. 데이터의 상태에 따라서 새롭게 데이터를 가져오거나 보관하고 있는 데이터를 활용하게 된다.
* 리액트 쿼리 개발툴에서 서버에서 데이터를 가져오는 동안에는 fetching 상태가 되고, 데이터 처리가 완료되면 fresh 상태가 된다.

* 예제 코드에서는 staleTime 이라는 것을 지정해 주었는데, staleTime은 '얼마의 시간이 지나면 이 데이터는 더 이상 신선(fresh)하지 않은가?'를 의미한다. 신선(fresh)하지 않다는 의미는 시간이 지나서 이 데이터를 다시 사용해야 할 때는 다시 최신 데이터를 가져온다는 의미가 된다.
* 개발자 도구를 확인해 보면 fetching -> fresh -> 10초 후 stale 상태가 되는 것을 확인할 수 있다. 리액트 쿼리는 기본적으로 다시 현재 화면이 활성화될 때 리액트 쿼리가 이를 체크하는데 다른 프로그램을 선택한 후에 다시 리액트 애플리케이션을 선택해 보면 10초 이내에는 서버를 다시 호출하지 않는 것을 볼 수 있다(stale이라는 의미가 '신선하지 않은, 상한'의 의미를 담고 있다).
* useQuery()는 기본적으로 현재 브라우저(윈도)가 활성화되면 다시 서버를 호출하는(refetch) 옵션이 지정되어 있다(refetchOnWindowFocus). 이 때 Stale 상태의 데이터는 이미 오래된 데이터라고 판단되므로 다시 서버를 호출하게 된다. 만일 staleTime이 길게 지정되면 fresh한 상태가 오래 지속되므로 다시 서버를 호출하지 않게 된다. useQuery()의 다양한 옵션은 아래 주소에서 확인할 수 있다.
https://tanstack.com/query/v4/docs/framework/react/reference/useQuery

useQuery | TanStack Query Docs

tanstack.com