ReactJS

React - TanStack Query

jjangsh 2024. 9. 23. 21:14

(1) 등장배경

(1)-1. 비동기 로직의 복잡성 해결 필요

  • 기존의 useEffect와 useState를 사용한 비동기 데이터 처리 방식은 상태 관리가 복잡하고 코드 중복이 많아 유지보수가 어려웠어요.
  • Redux Thunk와 같은 미들웨어를 사용해도 비동기 로직의 테스트가 복잡하고 보일러플레이트 코드가 많이 생기기 때문에 더 효율적인 도구가 필요했습니다.

 

(1)-2. 서버 상태 관리의 어려움

  • 서버 상태는 클라이언트 상태와 달리 캐싱, 동기화, 재검증 등 관리해야 할 요소가 많아 기존 방법으로는 관리가 어려웠어요.
  • 이를 해결하기 위해 등장한 것이 TanStack Query로, 서버 상태 관리를 쉽게 해 주고 복잡한 비동기 로직을 단순화해 줍니다.

 

(2) 개념

(2)-1. 서버 상태 관리 라이브러리

  • TanStack Query는 서버 상태를 관리하기 위한 라이브러리로, 데이터를 패칭하고 캐싱, 동기화, 무효화 등의 기능을 제공합니다.
  • 개발자는 이전에 비해 ‘훨씬’ 비동기 로직을 간편하게 작성하고 유지보수성을 높일 수 있습니다.

 

(2)-2. 주요 기능

  • 데이터 캐싱: 동일한 데이터를 여러 번 요청하지 않도록 캐싱하여 성능을 향상합니다.
  • 자동 리페칭: 데이터가 변경되었을 때 자동으로 리페 칭하여 최신 상태를 유지합니다.
  • 쿼리 무효화: 특정 이벤트가 발생했을 때 쿼리를 무효화하고 데이터를 다시 가져올 수 있습니다.

 

(3) 사용방법 - useQuery

(시작하기 전) 세팅

 

 - 프로젝트 생성

yarn create vite tanstack-query-app --template react

 

 

- 설치

yarn add @tanstack/react-query

 

 

- 적용할 범위(ex : 전역)에 Provider를 이용하여 적용 ( App.jsx 또는 main.jsx(index.jsx)에 세팅하는 것을 권장 )

// main.jsx

import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

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

 

 

(3)-1. useQuery의 개념

- useQuery는 데이터를 가져오기 위해 사용되는 TanStack Query의 대표적인 훅입니다. 쿼리 키비동기 함수(패칭 함수)를 인자로 받아 데이터를 가져오고, 로딩 상태, 오류 상태, 그리고 데이터를 반환합니다.

 

💡 로딩 상태, 오류 상태 등을 자동으로 반환하기 때문에 redux-thunk에서처럼 일일이 모든 상태를 직접 세팅할 필요가 없음.

 

- 이 훅을 통해 서버에서 데이터를 가져오고, 가져온 데이터를 컴포넌트에서 쉽게 사용할 수 있습니다

 

(3)-2. useQuery 기본 사용법

fetchTodos와 같은 비동기 함수는 별도 파일에 분리 보관하는 것이 유지보수 측면에서 좋지만 지금은 기본 사용법을 익히는 데에 집중하기 위해 한 파일에서 봅니다.

(테스트 환경은 json-server 사용, 아래는 db.json 파일)

{
  "todos": [
    {
      "id": "1715926482394",
      "title": "리액트 공부하기",
      "isDone": true
    },
    {
      "id": "1715926492887",
      "title": "Node.js 공부하기",
      "isDone": true
    },
    {
      "id": "1715926495834",
      "title": "영화보기",
      "isDone": false
    }
  ]
}

 

import { useQuery } from "@tanstack/react-query";
import axios from "axios";

const App = () => {
  const fetchTodos = async () => {
    const response = await axios.get("http://localhost:4000/todos");
    return response.data;
  };

  const {
    data: todos,
    isPending,
    isError,
  } = useQuery({
    queryKey: ["todos"],
    queryFn: fetchTodos,
  });

  if (isPending) {
    return <div>로딩중입니다...</div>;
  }

  if (isError) {
    return <div>데이터 조회 중 오류가 발생했습니다.</div>;
  }

  return (
    <div>
      <h3>TanStack Query</h3>
      <ul>
        {todos.map((todo) => {
          return (
            <li
              key={todo.id}
              style={{
                display: "flex",
                alignItems: "center",
                gap: "10px",
                backgroundColor: "aliceblue",
              }}
            >
              <h4>{todo.title}</h4>
              <p>{todo.isDone ? "Done" : "Not Done"}</p>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default App;

 

 

(4) 사용방법 - useMutation

(4)-1. useMutation의 주요 개념

  • useMutation은 데이터를 생성, 수정, 삭제하는 등의 작업에 사용되는 훅입니다. CUD에 대한 비동기 작업을 쉽게 수행하고, 성공 또는 실패 시에 추가적인 작업을 실행할 수 있기 때문에 useQuery와 함께 가장 대표적인 TanStack Query hook이라고 할 수 있어요.
  • 비동기 작업을 쉽게 처리한다는 말 안에는 작업이 완료된 후에 관련된 쿼리를 무효화하는 과정이 포함되는데 이 역시도 TanStack Query의 핵심 개념이라고 할 수 있습니다. 최신 데이터를 유지하는 데에 필수적인 요소예요.

(4)-2. useMutation 기본 사용법

import { useMutation, useQuery } from "@tanstack/react-query";
import axios from "axios";
import { useState } from "react";

const App = () => {
  const [todoItem, setTodoItem] = useState("");

  const fetchTodos = async () => {
    const response = await axios.get("http://localhost:4000/todos");
    return response.data;
  };

  const addTodo = async (newTodo) => {
    await axios.post("http://localhost:4000/todos", newTodo);
  };

  const {
    data: todos,
    isPending,
    isError,
  } = useQuery({
    queryKey: ["todos"],
    queryFn: fetchTodos,
  });

  const { mutate } = useMutation({
    mutationFn: addTodo,
  });

  if (isPending) {
    return <div>로딩중입니다...</div>;
  }

  if (isError) {
    return <div>데이터 조회 중 오류가 발생했습니다.</div>;
  }

  return (
    <div>
      <h3>TanStack Query</h3>
      <form
        onSubmit={(e) => {
          e.preventDefault();

          const newTodoObj = { title: todoItem, isDone: false };

          // useMutation 로직 필요
          mutate(newTodoObj);
        }}
      >
        <input
          type="text"
          value={todoItem}
          onChange={(e) => setTodoItem(e.target.value)}
        />
        <button>추가</button>
      </form>
      <ul>
        {todos.map((todo) => {
          return (
            <li
              key={todo.id}
              style={{
                display: "flex",
                alignItems: "center",
                gap: "10px",
                backgroundColor: "aliceblue",
              }}
            >
              <h4>{todo.title}</h4>
              <p>{todo.isDone ? "Done" : "Not Done"}</p>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default App;

 

 

(5) 사용방법 - invalidateQueries

(5)-1. invalidateQueries의 개념

  • invalidateQueries는 특정 쿼리를 무효화하여 데이터를 다시 패칭 하게 하는 함수입니다. 주로 useMutation과 함께 사용하여 데이터가 변경된 후 관련 쿼리를 다시 가져오도록 합니다.
  • 이를 통해 데이터가 항상 최신 상태로 유지될 수 있도록 도와줍니다. 예를 들어, 새로운 할 일을 추가한 후 기존의 할 일 목록을 다시 가져오도록 할 수 있습니다.

 

(5)-2. invalidateQueries 기본 사용법

invalidateQueries 로직 추가

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import { useState } from "react";

const App = () => {
  const queryClient = useQueryClient();

  const [todoItem, setTodoItem] = useState("");

  const fetchTodos = async () => {
    const response = await axios.get("http://localhost:4000/todos");
    return response.data;
  };

  const addTodo = async (newTodo) => {
    await axios.post("http://localhost:4000/todos", newTodo);
  };

  const {
    data: todos,
    isPending,
    isError,
  } = useQuery({
    queryKey: ["todos"],
    queryFn: fetchTodos,
  });

  const { mutate } = useMutation({
    mutationFn: addTodo,
    onSuccess: () => {
      // alert("데이터 삽입이 성공했습니다.");
      queryClient.invalidateQueries(["todos"]);
    },
  });

  if (isPending) {
    return <div>로딩중입니다...</div>;
  }

  if (isError) {
    return <div>데이터 조회 중 오류가 발생했습니다.</div>;
  }

  return (
    <div>
      <h3>TanStack Query</h3>
      <form
        onSubmit={(e) => {
          e.preventDefault();

          const newTodoObj = { title: todoItem, isDone: false };

          // useMutation 로직 필요
          mutate(newTodoObj);
        }}
      >
        <input
          type="text"
          value={todoItem}
          onChange={(e) => setTodoItem(e.target.value)}
        />
        <button>추가</button>
      </form>
      <ul>
        {todos.map((todo) => {
          return (
            <li
              key={todo.id}
              style={{
                display: "flex",
                alignItems: "center",
                gap: "10px",
                backgroundColor: "aliceblue",
              }}
            >
              <h4>{todo.title}</h4>
              <p>{todo.isDone ? "Done" : "Not Done"}</p>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default App;

'ReactJS' 카테고리의 다른 글

React - Zustand  (1) 2024.09.25
React - ( React Hooks 6 - Custom Hooks )  (2) 2024.09.13
React - ( React Hooks 5 - memoization )  (0) 2024.09.10
React - ( React Hooks 4 - useContext )  (0) 2024.09.06
React - ( React Hooks 3 - useRef )  (1) 2024.09.05