React-Query
React-Query

 

이전 SWR 포스팅을 작성했는데요. SWR보다 react-query 다운로드 수가 2배가량 높습니다. 원티드 프리온보딩 챌린지를 하면서도 사용했고 점점 대중화되어가는 눈치입니다. 부랴부랴 포스팅 남겨봅니다.

 

다행히도 Next.js와 리액트에서 SWR을 사용해 본 경험이 있어 덕분에 리액트 쿼리를 이해하는데 오래 걸리지 않았습니다.

 

 

[React] SWR(Stale-While-Revalidate)

최근 프론트엔드와 관련 한 카카오톡 오픈 채팅방을 통해 스터디를 시작했고 관련해서 공부했던 내용을 발표해야했기 때문에 포스팅을 진행해봤습니다. SWR이란? 클라이언트는 서버에서 데이터

juni-official.tistory.com

 

React-Query


리액트 쿼리는 리액트 내에서 서버의 상태(데이터), 캐싱, 동기화 및 상태 업데이트를 쉽게 도와주는 라이브러리로,  말이 어려운데 HTTP 통신과 서버에서 받은 데이터를 캐싱하고 설정에 기반해 클라이언트 데이터를 서버 데이터로 동기화해 주는 라이브러리입니다.

 

저 나름대로 리액트 쿼리를 정의해 봤는데요. 클라이언트에서 서버 데이터를 현명한 접근 방식으로 처리해주는 라이브러리! 어떤가요? 🤔

 

두리뭉실한데, 의미를 좁혀보자면 데이터 통신, 캐싱, 업데이트, 에러핸들링, 효율적인 비동기 과정을 처리해 줍니다.

 

Redux vs React-Query


리액트 쿼리에서 자주 나오는 말은 리덕스를 리액트 쿼리가 대체할 수 있다는 얘기입니다.

 

리액트를 공부했던 분들이라면 리덕스라는 높은 러닝커브의 라이브러리를 알고 있을 텐데요. 보통의 리액트 커리큘럼에는 꼭 들어가 있는 라이브러리지만 여기에 비동기 작업에 사용해야 하는 미들웨어(saga, thunk)를 또 학습해야 합니다. 리덕스 자체로도 코드나 파일 분리가 생겨 복잡도를 상승시키는데 추가로 미들웨어까지 붙여야 한다니, 개인적으로 답답할 따름입니다.

 

하지만 리덕스는 리덕스에 맞는 데이터를, 리액트 쿼리는 리액트 쿼리에 맞는 데이터를 다루는 데 사용하면 될 것 같습니다. 둘 다 다루는 데이터의 관심사가 달라서 Redux vs React-Query의 구도가 맞는지는 의문입니다.

 

Reference


 

[React, Redux] 내가 리덕스를 쓰지 않는 이유

안녕하세요, Einere입니다. (광고차단 기능을 꺼주시면 감사하겠습니다.) 해당 포스트는 Why I Stopped Using Redux를 번역한 글입니다. 리덕스는 리액트 환경에서 혁신적인 기술입니다. 리덕스는 불변

kjwsx23.tistory.com

 

 

찜으로 찜해보는 react-query - 트렌비 기술블로그

도입배경

tech.trenbe.com

 

 

React-Query 설치


> yarn add @tanstack/react-query

> npm i @tanstack/react-query

 

React-Query 사용하기


// src/index.tsx

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 queryOptions = {
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      retry: 0,
    },
  },
}

const queryClient = new QueryClient(queryOptions);

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)

root.render(
	<QueryClientProvider client={queryClient}>
		<ReactQueryDevtools />
		<App />
	</QueryClientProvider>
)

 

리액트 엔트리 포인트에서 리액트 쿼리를 사용할 수 있도록 Provider로 감싸주기

 

queryOptions

  • staleTime: 캐시가 fresh 상태로 유지되는 시간 (default: 0)
  • cacheTime: 메모리에 캐시가 남아있는 시간으로, 만료 시 가비지 컬렉션이 데이터를 처리 (default: 5*60*1000 => 5 min)
  • enable: 자동으로 데이터를 불러옴 (default: true)
  • retry: API호출 실패 시 자동으로 재요청, true일 경우 무한 재요청, Number로 입력할 경우 N번 재요청  (default: false)
  • onSuccess/onError/onSettled/select: 성공/실패/무조건실행(data: 성공하면 데이터,  error: 에러)/성공 시 데이터 가공
  • keepPrevioueData: 새로 가져온 데이터를 화면에 나오기 전까지 기존 데이터를 화면에 유지할지 여부

 

useQuery / useMutation

useQuery: GET Method API를 호출할 때 사용하는 훅
useMutation: POST / PUT / DELETE Method는 useMutation 훅을 사용

 

example


import { useQuery } from '@tanstack/react-query'

export default function Todos() {

  // 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>
  )

 

관심사 분리를 통해 React-Query 사용

 


Project
│
├─ src
│  ├─ api           (🌏) => API 함수 모음
│  │  └─ todo.tsx
│  ├─ hooks         (🕹️) => 커스텀 훅 모음
│  │  ├─ mutation  (📫) useMutation 훅
│  │  └─ query     (🕸️) useQuery 훅
...

 

 

제 경우 기존 개발 로직에서는 api 디렉토리에 정의한 함수를 컴포넌트 로직에서 불러와 별도의 함수를 선언해 try-catch문으로 정의해 주고 useEffect 훅으로 호출해 주는 방식으로 사용했는데요. 

 

API -> Component( + API 로직 )

 

API(Rest) / Hooks(query, mutation) / Component 이렇게 관심사를 분리해서 코드를 작성할 경우 각 영역 별로 코드의 목적을 명확하게 알 수 있고 유지보수나 재사용성이 높아집니다.

 

API -> Hooks(query/mutation) -> Component

 

가운데 추가된 Hooks의 역할은 API 호출뿐만 아니라 캐싱처리 및 에러 핸들링이나 데이터 가공 등 Rest API를 호출하고 진행해야 하는 로직을 처리하고 그 결과는 컴포넌트가 전달받아 렌더링 해줍니다.

 

기존 개발 로직에서는 하나의 컴포넌트에 많은 로직이 포함 됐다면 지금은 관심사 및 기능이 명확히 분리되어 코드가 정리됩니다.

 

API

// src/api/todo.js

import axios from 'axios

export const getTodoList = () => {
  const url = '/todos'
  return axios.get(url)
}

Hooks

// src/hooks/query/useGetTodos.js

import { useQuery } from 'react-query'
import { getTodoList } from '../../api/todo'

const useGetTodos = () => {
  return useQuery(['getTodoList'], () => getTodoList(), {
    select: (data) => {
      // data.data.reverse() // 데이터 가공
      return data
    },
    staleTime: 30000,
    cacheTime: Infinity,
  })
}

export default useGetTodos

Component

import { useState } from 'react'
import useGetTodos from '../../hook/query/useGetTodos'
import { Link } from 'react-router-dom'

const Home = () => {
  const [list, setList] = useState([])
  const { data, isLoading } = useGetTodos()

  useEffect(() => {
    if (data) setList(data.data)
  }, [data])

  if (isLoading) return <>로딩중 ...</>

  return (
    <ul>
      {list &&
        list.map((todo) => (
          <li key={todo.id}>
            <Link to={`/todo/${todo.id}`}>
              <span className="todo-title">{todo.title}</span>
              <span className="todo-date">{dateFormatter(todo.createdAt)}</span>
            </Link>
          </li>
        ))}
    </ul>
  )
}
export default Home

 

참조


 

리액트 쿼리 useQuery 사용법

리액트 쿼리는 fetching, caching, updating 등을 다양한 옵션과 간단한 로직으로 해결할 수 있게 해줍니다. 그 중에서 useQuery는 GET 요청을 보낼 때 사용합니다. 초기 설정은 Context API와 비슷합니다. Contex

velog.io

 

[tanstack-query] tanstack query 공식 문서 간단 정리(feat. react query)

보통 서버 state(ajax를 통해 서버에서 가져오는 데이터)가 있고 클라이언트 state가 있는데 데이터 fetching 해서 불러오는 데이터는 서버 state로 관리하는 게 좋다. tantstack-query의 존재를 알기 전엔 서

velog.io