본문 바로가기

React/실험실

[React] React-Query - 겉핥기

들어가며

지난번 React-Query에 대한 필요성을 간략하게 알아봤다. 

이번에는 React-Query 자체에 대해서 정리를 해보려고 한다.

 

React-Query ? 

서버에서 값을 가져오거나, 캐싱, 값 업데이트, 에러 핸들링 등 비동기 과정을 사용할 때 

도움을 준다. 

 

기존 Redux, Mobx, Recoil 같은 상태 관리 라이브러리가 있지만, 서버 데이터와 클라이언트

데이터를 분리시킬 수 있다는 장점이 있다. 

 

React-Query의 장점

▶ 서버의 상태 업데이트, 데이터 패칭, 캐싱 등을 쉽게 할 수 있도록 도와준다.

 

Redux 등, 전역 상태 관리를 통해서 서버의 데이터를 비동기로 가져오기 위해서는 
     추가적으로 작성해야하는 코드 많지만 React-Query는 간단하게 처리해준다. 

 

  코드가 간단해진다는 것은 유지 보수를 쉽게할 수 있다는 뜻이 된다.

 

  같은 데이터를 여러번 요청할 경우 중복을 제거해준다.

 

  백그라운드에서 자동으로 데이터를 업데이트시켜준다.

이 외에도 많지만 이것은 좀 더 React-Query와 친해지고 더 공부해보자! 

 

React-Query의 라이프사이클

React Query는 5단계로 라이프 사이클이 구성되어 있다. 

Fetching : 데이터를 요청한 상태 

 

  Fresh : 데이터가 만료되지 않은 상태 
    ▷ 컴포넌트의 상태가 변경되어도 데이터를 다시 요청하지 않는다. 

     새로고침을 하면 다시 Fetching 한다. 

 

▶ Stale : 데이터가 만료된 상태 

     서버에서 데이터를 받는 사이에 다른 유저가 데이터를 추가, 수정, 삭제등을 해서 최신화가
        필요한 상태를 말한다. 

     컴포넌트가 마운트, 업데이트되면 다시 데이터를 요청한다. 

     Fresh 상태에서 Stale 상태로 변경되는데 걸리는 시간이 StaleTime이다

 

Inactive : 사용하지 않는 상태

     일정 시간이 지나면 가비지 콜렉터가 캐시에서 제거한다. 

     기본값은 5분이다. 

     Inactive 상태에서 캐싱된 상태로 남아있는 시간을 CacheTime이라고 한다. 

 

Delete : 가비지 콜렉터에 의해 캐시에서 제거된 상태 

환경 설정 

npx react-create-app client

cd client 
npm install react-query

프론트 환경에서는 CRA를 사용해서 간단하게 만들고 react-query를 설치했다. 

 

express server

서버 환경은 express 를 사용해서 간단하게 구성했다.

 

백엔드 준비

// routes/index.js 

var express = require("express");
var router = express.Router();

const dummyList = [
  { title: "title1", contents: "text1", isFinish: false },
  { title: "title2", contents: "text2", isFinish: false },
  { title: "title3", contents: "text3", isFinish: true },
  { title: "title4", contents: "text4", isFinish: true },
  { title: "title5", contents: "text5", isFinish: false },
  { title: "title6", contents: "text6", isFinish: false },
];

/* GET home page. */
router.get("/api", function (req, res, next) {
  // console.log(dummyList);
  res.status(202).json(dummyList);
});

router.post("/api", function (req, res, next) {
  dummyList.push(req.body);

  res.status(201);
});

module.exports = router;

간단하게 " /api " 경로로 get 요청하면 dummyList 데이터를 넘겨주는 API이다.

post 요청의 경우 업데이트를 해주고 있다. 

 

프론트엔드 설정하기

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

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

const queryClient = new QueryClient();

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

QueryClient를 사용해서 client를 먼저 만들어줬습니다. 

여기서 다양한 옵션을 설정할 수 있는데, 이것도 모두 공부하고 지나가면 좋겠지만 

분량이 많아질 것 같아서 다음에 공부하자..! ( 점점 할게 많아지는.... )

 

useQuery

import "./App.css";
import { useQuery } from "react-query";
import axios from "axios";

const getTodos = async () => {
  const { data } = await axios.get("/api");

  return data;
};

function App() {
  const query = useQuery("todos", getTodos);
  console.log(query);

  return (
    <div className="App">
      {query.data.map((data) => {
        return <div key={data.title}>{data.title}</div>;
      })}
    </div>
  );
}

export default App;

useQuery를 사용해서 데이터를 요청할 수 있다. 

query 안에는 data, isLoading, isSuccess, isError 등 많은 정보를 가지고 있다. 

 

만약 Redux였다면 직접 구현해야하는 부분을 React-Query는 알아서 제공을 해주고 있다. 

API 요청이 완료되기 전에는 status 상태가 loading인데, 완료되면 success or failed로 나뉜다. 

 

실제 결과 데이터는 data에 들어가있다. 

 

useMutation

import "./App.css";
import { useMutation, useQuery } from "react-query";
import axios from "axios";

const getTodos = async () => {
  const { data } = await axios.get("/api");

  return data;
};

const postTodos = (data) => {
  axios.post("/api", data);
};

function App() {
  const query = useQuery("todos", getTodos);
  const mutation = useMutation(postTodos, {
    onSuccess: () => {
      console.log("Update Todos");
    },
  });

  const handleButtonClick = () => {
    mutation.mutate({ title: "title7", contents: "text7", isFinish: false });
  };

  return (
    <div className="App">
      {query.status === "success" &&
        query.data.map((data) => {
          return <div key={data.title}>{data.title}</div>;
        })}

      <button onClick={handleButtonClick}>버튼</button>
    </div>
  );
}

export default App;

useMutation을 사용해서 데이터를 업데이트할 수 있다. 

이때, 데이터를 추가하고 추가한 값은 실시간으로 업데이트 되고 있지 않다. 

 

이것을 useMutation은 get을 한번 더 하는 방법과 기존 값을 업데이트하는 방법 2개를 선택할 수 있다. 

 

한번 더 요청하기

function App() {
  const queryClient = useQueryClient();
  
  const query = useQuery("todos", getTodos);

  const mutation = useMutation(postTodos, {
    onSuccess: () => {
      queryClient.invalidateQueries("todos");
    },
  });
  
  // ...
}

useQueryClient를 사용하면 client에 접근할 수 있게 되는데, invalidateQueries는 

기존 데이터를 무효화하고 다시 가져올 수 있게 해주는 함수이다. 

 

키값 배열을 넣을 수 있고, 혹은 키값 하나만 넣을 수 있다. 

여기서 사용하는 키는 useQuery를 사용할 때 첫 번째 매개변수이다. 

 

기존 값 업데이트하기 

 const mutation = useMutation(postTodos, {
    onMutate: (data) => {
      const previousValue = queryClient.getQueryData("todos");
      console.log("previousValue", data);

      queryClient.setQueryData("todos", (old) => {
        console.log("old", old);
        return [...old, data];
      });

      return previousValue;
    },
    onSuccess: (result, variables, context) => {
      console.log("성공 메시지:", result);
      console.log("변수", variables);
      console.log("onMutate에서 넘어온 값", context);
    },
  });

서버에 다시 요청하는 방법은 간단하지만 결국 서버의 리소스를 사용하는 것이라 꺼려지게 된다. 

그래서 기존 값을 업데이트 하는 경우가 있을 텐데, React Query에서는 그나마 간단하게 가능하다. 

 

queryClient의 getQueryData는 요청했던 데이터를 가져올 수 있고, 

setQueryData는 해당 키의 데이터를 수정할 수 있다.

그리고 data는 mutation을 통해 넘긴 데이터를 가져올 수 있다. 

 

이것을 활용해서 서버에 직접 요청을 할 필요없이 데이터를 업데이트할 수 있다. 

 

이번에는 정말 간단하게 React-Query에 대해서 알아봤다. 

한번에 최대한 많은 내용을 담는 것도 좋겠지만 큰 틀을 이해하고 하나씩 공부하는 것도 좋다고 생각해서

이번에 부족했던 부분은 다시 공부해서 작성하겠다. 

반응형