[TIL] React-Query

·2023년 12월 5일
1

TIL

목록 보기
49/85
post-thumbnail

React-Query 란?

우리는 다른 서버와의 API 통신과 비동기 데이터 관리를 위해 Redux-thunk, Redux-saga 등의 미들웨어를 채택해서 사용했다. 그러나 다음과 같은 문제가 있다.

  • 코드량이 너무 많다.
  • Redux가 비동기 데이터 관리를 위한 전문 라이브러리가 아니다. (규격화 문제)

React-Query는 서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러핸들링 등 비동기 과정을 더욱 편하게 하는데 사용하기 위한 라이브러리 이다!

사용하는 이유

여러가지 장점이 있지만, 주로 아래와 같은 이유로 사용한다.

  • 캐싱
  • get을 한 데이터에 대해 update를 하면 자동으로 다시 get을 수행
  • 데이터가 오래 되었다고 판단되면 다시 get (invalidateQueries)
  • 동일 데이터 여러번 요청하면 한 번만 요청
  • 비동기 과정을 선언적으로 관리 가능

사용하기

설치

// v3
$ yarn add react-query

// v5
$ yarn add @tanstack/react-query
$ yarn add @tanstack/react-query-devtools

v4 부터 이름이 React-query 에서 TanStack Query로 바뀌었다. 사용 방법도 약간 다르지만 거의 유사하다.

세팅

앱의 최상단에 QueryClientProvider를 통해 queryClient를 제공해줘야 한다.

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

const queryClient = new QueryClient();

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      {/* devtools */}
      <ReactQueryDevtools initialIsOpen={true} />
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

useQuery

  • 데이터를 get 하기 위한 api 이다. (CRUD 에서 R 에 해당)
  • 첫 번째 인자로 queryKey 가 들어간다 (unique 해야 함)
    • refetching 하는 데 쓰인다.
    • 캐싱(caching) 처리를 하는 데에도 쓰인다.
    • 앱 전체 맥락에서 이 쿼리를 공유하는 방법으로 쓰인다.
    • 어느 컴포넌트에 뿌려져 있어도 같은 queryKey면 같은 쿼리 및 데이터 보장
    • 쿼리 키는 string 과 array 를 받는다.
  • 두 번째 인자로 queryFn 이 들어간다. (api 호출 함수 -> 비동기함수)
    • queryFn은 Promise 객체를 리턴한다.
    • Promise는 다음 중 하나의 상태를 가진다. (pending, fulfilled, rejected)
  • useQuery의 리턴 값은 { isLoading, isError, data, error } 등이 들어있는 객체다.
  • useQuery는 비동기로 동작한다. 즉, 한 컴포넌트에 여러 개의 useQuery가 있다면, 하나가 끝나고 다음 useQuery가 실행되는 것이 아닌, 두 개의 useQuery가 동시에 실행된다. 여러 개의 비동개 query가 있다면 useQueries를 사용하기를 권장한다.

예시 (v3)

const Todos = () => {
  const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, {
    refetchOnWindowFocus: false, // react-query는 사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행합니다. 그 재실행 여부 옵션 입니다.
    retry: 0, // 실패시 재호출 몇번 할지
    onSuccess: data => {
      // 성공시 호출
      console.log(data);
    },
    onError: e => {
      // 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출됩니다.)
      // 강제로 에러 발생시키려면 api단에서 throw Error 날립니다. (참조: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default)
      console.log(e.message);
    }
  });

  if (isLoading) {
    return <span>Loading...</span>;
  }

  if (isError) {
    return <span>Error: {error.message}</span>;
  }

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
};

useMutation

  • 값을 바꿀 때 사용하는 api 이다. (CRUD에서 CUD에 해당)
  • useMutation의 리턴 값도 객체이며 { mutate, isError, isLoading, isSuccess, error, data } 등이 들어있다.

예시 (v3)

mutation 함수 선언

// src/api/mutiationFns
import axios from "axios";

const SERVER_URI = "http://localhost:4000";

export const addTodo = async (newTodo) => {
  await axios.post(`${SERVER_URI}/todos`, newTodo);
};

useMutation 사용하여 데이터 수정

// imput.jsx
...
import { addTodo } from "../../../api/todos";
import { QueryClient, useMutation } from "react-query";
...


function Input() {
...
	const queryClient = new QueryClient();
	
	const mutation = useMutation(addTodo, {
	  onSuccess: () => {
	    // Invalidate and refresh
	    // 이렇게 하면, todos라는 이름으로 만들었던 query를 invalidate (무효화)할 수 있어요.
        // addTodo가 성공하면 todos로 맵핑된 useQuery api 함수 실행⭐️
	    queryClient.invalidateQueries("todos");
	  },
  });
...
const handleSubmitButtonClick = (event) => {
    event.preventDefault();
	// 생략
    const newTodo = {
      title,
      contents,
      isDone: false,
      id: uuidv4(),
    };

    mutation.mutate(newTodo); // ⭐️ 여기
    setTitle("");
    setContents("");
  };

[Invalidate의 과정]

  • input.jsx에서 값 입력으로 인해 서버 데이터가 변경됨
  • onSuccess 가 일어나면 기존 query인 "todos"는 무효화
  • 새로운 데이터를 가져와서 "todos"를 최신화 시킴
  • "todos"라는 같은 쿼리 키를 사용한 컴포넌트인 TodoList.jsx도 갱신됨

v5

v3에서는 useQuery를 사용할 때 첫 번째 인자(queryKey), 두 번째 인자(queryFn) 를 넣어주었다. 그리고 useMutation을 사용할 때는 첫 번째 인자(MutationFn)를 넣어주었다.

v5에서는 하나의 인자(객체) 에 이들을 모두 넣어주는 형식으로 약간 바뀌었다.
좀 더 명시적으로 바뀐 것 같다.

예시 (v5)

function Todos() {
  // Access the client
  const queryClient = useQueryClient()

  // Queries
  const query = useQuery({ queryKey: ['todos'], queryFn: getTodos })

  // Mutations
  const mutation = useMutation({
    mutationFn: postTodo,
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  return (
    <div>
      <ul>
        {query.data?.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>

      <button
        onClick={() => {
          mutation.mutate({
            id: Date.now(),
            title: 'Do Laundry',
          })
        }}
      >
        Add Todo
      </button>
    </div>
  )
}

redux-thunk를 쓰면서 비효율적이라고 생각했던 부분을 react-query가 해결해 줄 수 있을 것 같다. 아직 익숙하지 않아서 좀 더 써보면서 익혀야 겠지만..

profile
느리더라도 조금씩, 꾸준히

0개의 댓글