FE 이모저모 공부

React-Query에 대하여

58청춘 2024. 1. 18. 21:45
728x90

1. React-Query 란?

React-Query란 서버의 데이터를 가져오거나, 캐싱, 값 동기화 및 업데이트, 에러 처리 등 비동기 과정을 쉽게 할 수 있도록 도와주는 라이브러리이다.

 

1.1 사용 이유

기존 상태 관리 라이브러리(redux 등)는 클라이언트 상태 관리에는 용이하지만, 서버 상태를 관리하기는 어렵다.

클라이언트에서 사용하는 상태가 변할 수 있는 서버의 상태와 다를 수 있기 때문이다.

 

즉, API 요청과 비동기 데이터 관리를 좀더 쉽게 하기위해 등장한 것이다.

 

React-Query는 서버의 상태를 불러오고, 불러온 상태를 캐싱하며, 지속적으로 동기화하고 업데이트 하는 작업을 도와준다.

 

또한 기존 상태 관리 라이브러리를 이용하면 서버의 데이터와 클라이언트 데이터를 store에 공존하게 될 수 있다.

(이에 대해서는 해당 문제에 대해 정리한 글이 있다.)

 

1.2 React-Query 장점

  • 캐싱을 해준다.
  • get한 데이터에 대해 update 하면 자동으로 get을 재실행해준다.
    (게시판 글을 가져오고 글을 생성하면 글을 가져오는 api를 자동으로 재실행)
  • 데이터가 오래 되었다고 판단하면 다시 get한다.(invalidateQueries)
  • 동일한 데이터 요청은 한번만 요청한다.
    (옵션에 따라 중복 호출 허용 시간 조절이 가능하다.)
  • 무한 스크롤 기능을 쉽게 구현할 수 있다.(Infinite Queries)
  • 비동기 과정을 선언적으로 관리할 수 있다.
  • Reack Hook과 사용하는 구조가 유사하다.

 

2. React-Query 사용하기

React-Query를 사용하기 위해 초기 세팅을 시작하자.

npm i react-query

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}>
      <ReactQueryDevtools initialIsOpen={true} />
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

 

2.1 useQuery

HTTP 메서드 중 GET 요청과 같이 서버에 저장되어 있는 "상태"를 불러와 사용 할 때 사용한다.

const { data } = useQuery(
  queryKey, // 이 Query 요청에 대한 응답 데이터를 캐시할 때 사용할 Unique Key (required)
  fetchFn, // 이 Query 요청을 수행하기 위한 Promise를 Return 하는 함수 (required)
  options, // useQuery에서 사용되는 Option 객체 (optional)
);

 

 

useQuery는 Unique Key(Query Key)를 이용해 가져온 서버 상태를 로컬에 캐시하고 관리할 수 있다.

다른 컴포넌트에서 해당 Unique Key를 사용하는 useQuery Hook이 있으면 캐시된 데이터를 우선적으로 사용한다.
queryKey는 문자열과 배열을 전달 할 수 있는데, 배열로 넘기면 0번 값은 string값으로 다른 컴포넌트에서 부를 값이 들어가고 두번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달된다.

 

fetchFn은 비동기 함수(api 호출 함수)가 들어간다.(Promise가 들어가야 한다)

 

const Todos = () => {
  const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, {
    // react-query는 사용자가 사용하는 윈도우가 다른 곳을 갔다가 
    // 다시 화면으로 돌아오면 이 함수를 재실행합니다. 그 재실행 여부 옵션 입니다.
    refetchOnWindowFocus: false, 
    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>
  );
};
function Todos() {
  const { status, data, error } = useQuery("todos", fetchTodoList);

  if (status === "loading") {
    return <span>Loading...</span>;
  }

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

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

 

useQuery는 비동기로 동작해서 한 컴포넌트에 여러개의 useQuery가 비동기적으로 동시에 실행된다.

여러개의 useQuery를 사용하기 보다는, useQueries를 사용해 구현하는 것이 좋다.

 

useQuery 동기적으로 실행

enabled 옵셔을 사용하면 useQuery를 동기적으로 사용할 수 있다.

const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList);
const { data: nextTodo, error, isFetching } = useQuery(
  "nextTodos",
  fetchNextTodoList,
  {
    enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다
  }
);

 

 

2.2 useQueries

useQuery를 여러번 사용해야 하는 경우에 사용해주면 좋다.

unique key를 배열로 넣으면 query 함수 내부에서 변수로 사용할 수 있다.
const test = {
  version: "12.1.1"
};

const result = useQueries([
  {
    queryKey: ["getRune", test.version],
    queryFn: params => {
      console.log(params); // {queryKey: ['getRune', '12.1.1'], pageParam: undefined, meta: undefined}

      return api.getRunInfo(test.version);
    }
  },
  {
    queryKey: ["getSpell", test.version],
    queryFn: () => api.getSpellInfo(test.version)
  }
]);

 

 

2.3 QueryCache

쿼리에 대한 성공 실패 전처리를 할 수 있다.

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      console.log(error, query);
      if (query.state.data !== undefined) {
        toast.error(`에러!!: ${error.message}`);
      },
    },
    onSuccess: res => {
      console.log(res)
    }
  })
});

 

onError와 onSuccess를 이용해 전처리를 할 수 있다.

 

2.4 useMutation

const { mutate } = useMutation(
  mutationFn, // 이 Mutation 요청을 수행하기 위한 Promise를 Return 하는 함수 (required)
  options, // useMutation에서 사용되는 Option 객체 (optional)
);

값을 바꿀때 사용하는 api이며, HTTP 메서드 중 PUT, POST, DELETE 요청과 같이 부수효과를 발생시켜 서버의 상태를 바꿀때 사용한다.

 

첫번째 파라미터는 Mutation 요청을 수행하기 위한 Promise를 return 하는 함수이며,

useMutation의 return 값 중 mutate(또는 mutateAsync)함수를 호출하여 서버에 부수효과를 발생시킬 수 있다.

 

function NotificationSwitch({ value }) {
  // mutate 함수를 호출하여 mutationFn 실행
  const { mutate, isLoading } = useMutation(
    (value) => axios.post(URL, { value }), // mutationFn
  );

  return (
    <Switch
      checked={value}
      disabled={isLoading}
      onChange={(checked) => {
        // mutationFn의 파라미터 'value'로 checked 값 전달
        mutate(checked);
      }}
    />
  );
}

 

이때 update이후 get을 다시 시켜주려면 mutate 함수가 성공할 때 unique key로 맵핑된 get 함수를 invalidateQueries에 넣어주면 된다.

import axios from 'axios';
import { useMutation, useQueryClient } from 'react-query';
import { QUERY_KEY as todosQueryKey } from './useTodosQuery';

// useMutation에서 사용할 `서버에 Side Effect를 발생시키기 위해 사용할 함수`
// 이 함수의 파라미터로는 useMutation의 `mutate` 함수의 파라미터가 전달됩니다.
const fetcher = (contents: string) => axios.post('/todos', { contents });

const useTodosMutation = () => {
  // mutation 성공 후 `useTodosQuery`로 관리되는 서버 상태를 다시 불러오기 위한
  // Cache 초기화를 위해 사용될 queryClient 객체
  const queryClient = useQueryClient();

  return useMutation(fetcher, {
    // mutate 요청이 성공한 후 queryClient.invalidateQueries 함수를 통해
    // useTodosQuery에서 불러온 API Response의 Cache를 초기화
    onSuccess: () => queryClient.invalidateQueries(todosQueryKey),
  });
};

export default useTodosMutation;

 

728x90