devdubby
내가 배운것들 끄적끄적✏️
devdubby
전체 방문자
오늘
어제

티스토리

  • 전체보기 (18)
    • Frontend (11)
    • 커리어 (2)
    • TIL (0)
    • 일상&생각 (3)
    • 서버 (0)
    • 글또 (1)
    • 회고 (0)

태그

  • 백엔드
  • react
  • 프론트엔드 기술 세미나
  • VirtualDom
  • TDD
  • yarn berry
  • 유데미
  • 모노레포
  • 웹 성능 지표
  • Lighthouse
  • frontend
  • udemy
  • reactQuery
  • 프론트엔드 세미나
  • SSR
  • webpack
  • React Query
  • 웹 성능 최적화
  • TanstackQuery
  • spring boot
  • next.js
  • 모듈시스템
  • 글또
  • yarn workspace
  • 개발블로그
  • til
  • 2022년 회고
  • 회고
  • monorepo
  • state of css

최근 글

블로그 메뉴

  • 홈
  • 태그
  • 방명록
hELLO · Designed By 정상우.
devdubby

내가 배운것들 끄적끄적✏️

React Query는 어떤 기술일까?
Frontend

React Query는 어떤 기술일까?

2022. 12. 11. 16:14

현재 재직중인 가상자산 거래소 회사에서 올해 2분기 프론트엔드 세미나 진행당시 발표했던 React Query에 대해 조사한 내용입니다.

React Query란 무엇인가요?

React Query는 Server State를 관리하는 라이브러리로 React 프로젝트에서 Server와 Client 사이 비동기 로직들을 손쉽게 다루게 해주는 도구입니다.
현재 저희의 store에는 Client State, Server State 할것 없이 많은 것들을 저장하고 있습니다. (ex. 유저정보, 배너정보, 코인 가격 등...)
React Query에서 Redux, Mobx 등 기존 상태 관리 라이브러리는 클라이언트 상태 작업에 적합하지만 비동기 또는 서버 상태 작업에는 그다지 좋지 않다고 말하고 있습니다.

공식 문서에서 Server State 의 특성을 파악할 때 다음과 같은 어려움이 있다고 말합니다.

  • 캐싱 하는것
  • 동일한 데이터에 대한 여러 요청을 단일 요청으로 중복 제거하는 것
  • 백그라운드에서 "오래된" 데이터를 업데이트 하는 것
  • 데이터가 "오래된" 경우를 파악 하는 것
  • 데이터 업데이트를 최대한 빨리 반영하는 것
  • 페이지네이션 및 Lazy 로딩 데이터와 같은 성능 최적화
  • 서버 상태의 메모리 및 가비지 수집 관리
  • 구조적 공유를 통한 쿼리 결과를 메모 하는 것

그래서 이러한 문제를 쉽게 해결하고 쉽게 Server State들을 관리하는데 React Query는 선언형으로 사용할 수 있고 커스터마이징이 가능한 최고의 라이브러리라고 소개하고 있습니다.
고팍스에서의 Server State들은 오래된 입/출금 내역, 오래된 매수/매도 내역, 항상 동일한 랜딩 페이지 팝업 내용, CEO Update, 미디엄 블로그 글? 등 정도가 있을 것 같습니다.

바로 코드를 한번 보겠습니다.

import { QueryClient, QueryClientProvider, useQuery } from 'react-query'

const queryClient = new QueryClient()

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

function Example() {
  const { isLoading, error, data } = useQuery('repoData', () =>
    fetch('https://api.github.com/repos/tannerlinsley/react-query').then(res =>
      res.json()
    )
  )

  if (isLoading) return 'Loading...'

  if (error) return 'An error has occurred: ' + error.message

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
      <strong>👀 {data.subscribers_count}</strong>{' '}
      <strong>✨ {data.stargazers_count}</strong>{' '}
      <strong>🍴 {data.forks_count}</strong>
    </div>
  )
}

React Query는 내부에 React.context를 사용하고 있기 때문에 QueryClientProvider 로 감싸주어 Global 하게 사용할 수 있도록 합니다.
실제로 같은 query key를 갖고 있다면 다른 컴포넌트에서 query의 결과를 꺼내올 수 있습니다.

그럼 React Query는 데이터를 어디에다 캐시를 해둘까요?
React Query Github 를 확인해보면 QueryCache 라는 class를 만들어 활용하는데 내부에서 hashMap 형태로 저장하여 활용하는 것을 볼 수 있습니다.

React Query의 핵심

React Query 의 핵심 개념에는 3가지가 있습니다.

  • Queries
  • Mutations
  • Query Invalidation

Queries

Queries에서는 보통 데이터를 API 요청을 통해 읽어올 때 사용합니다. CRUD의 기능 중 R 입니다.
useQuery는 (queryKey, queryFn, options) 로 parameter를 전달할 수 있게 이루어져 있습니다.
queryKey 는 유니크한 키 값이고 queryFn 으로 전달한 쿼리 함수가 성공적으로 실행되면 데이터를 반환하고 실패하면 error를 활용할 수 있습니다.
userQuery API Reference 를 보면 굉장히 많은 데이터를 활용할 수 있는것을 볼 수 있습니다.

  • isLoading 또는 status === 'loading' 은 현재 쿼리에 데이터가 없고 가져오는 중이라는 뜻입니다.
  • isError 또는 status === 'error' 은 쿼리에 오류가 발생했다는 것 입니다.
  • isSuccess 또는 status === 'success' - 쿼리가 성공했고 데이터를 활용할 수 있다는 것 입니다.
  • 쿼리가 isError 상태인 경우 error 프로퍼티를 사용할 수 있습니다.
  • 쿼리가 success 상태인 경우 data 프로퍼티를 사용할 수 있습니다.

useQuery에서 Query key는 다음과 같이 전달할 수도 있습니다.

// An individual todo
useQuery(['todo', 5], ...)
// queryKey === ['todo', 5]

// An individual todo in a "preview" format
useQuery(['todo', 5, { preview: true }], ...)
// queryKey === ['todo', 5, { preview: true }]

// A list of todos that are "done"
useQuery(['todos', { type: 'done' }], ...)
// queryKey === ['todos', { type: 'done' }]

Query Key는 해시되기 때문에 다음과 같은 경우에도 모두 같은 것으로 간주합니다.

useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)

그러나 배열 형태에서는 순서가 중요하기 때문에 아래 3가지 경우는 다른것으로 간주 됩니다.

useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)

만약에 여러 API 요청을 해야하는 경우에는 어떻게 해야 할까요? 아래와 같이 useQuery를 필요한 만큼 추가하면 React Query에서 알아서 병렬처리해 동시성을 극대화줍니다.

const usersQuery = useQuery('users', fetchUsers)
const teamsQuery = useQuery('teams', fetchTeams)
const projectsQuery = useQuery('projects', fetchProjects)

Mutations

Mutation에서는 보통 쿼리를 통해 데이터를 생성/업데이트/삭제할 경우 사용하곤 합니다.
useMutation hook 을 사용합니다.

function App() {
  const mutation = useMutation(newTodo => {
    return axios.post('/todos', newTodo)
  })

  return (
    <div>
      {mutation.isLoading ? (
        'Adding todo...'
      ) : (
        <>
          {mutation.isError ? (
            <div>An error occurred: {mutation.error.message}</div>
          ) : null}

          {mutation.isSuccess ? <div>Todo added!</div> : null}

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

isLoading, isSuccess, isError 등 Queries를 사용할때와 동일한데 다른점은 mutation 함수에 mutate 를 사용하여 변수나 객체를 전달하여 업데이트 할 수 있습니다.
useMutation API Reference 에도 여러가지 레퍼런스를 확인할 수 있습니다.
useMutation은 겉보기에는 업데이트 하는것 말고는 하는게 없어보이지만 QueryClient의 invalidateQueries method 와 함께 사용하면 더 시너지를 발휘할 수 있습니다.

import { useMutation, useQueryClient } from 'react-query'
const queryClient = useQueryClient()

// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
const mutation = useMutation(addTodo, {
  onSuccess: () => {
    queryClient.invalidateQueries('todos')
    queryClient.invalidateQueries('reminders')
  },
})

mutate 함수가 실행되기 전 성공 여부, 끝과 같이 라이프사이클에 따라 콜백함수를 작성할 수 있스ㅂ니다.

useMutation(addSuperHero, {
  onMutate: (variables) => {
    // mutate 함수가 실행되기 전에 실행
    console.log(variables) // addSuperHero에 들어가는 인자
  },
  onSuccess: (data, variables) => {
    // 성공
  },
  onError: (error, variables) => {
    // 에러 발생
  },
  onSettled: (data, error, variables, context) => {
    // 성공 or 실패 상관 없이 실행
  },
})

Query Invalidation

쿼리를 다시 가져오기 전에 쿼리가 항상 stale 하게 되는 것은 아닙니다. 이를 위해 쿼리를 지능적으로 오래된 것으로, stale 하게 표시하고 잠재적으로 다시 가져올 수 있는 QueryClient 의 invalidateQueries 메소드가 있습니다.

// Invalidate every query in the cache
queryClient.invalidateQueries()
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries('todos')

위와 같은 방법이 쿼리를 stale 하게, invalidate 하게 만드는 방법입니다. QueryClient API Reference 에도 많은 메소드를 확인할 수 있습니다. 용도에 따라 맞게 사용하면 좋을 것 같습니다.

이외에도 Infinite scroll 구현시 활용할 수 있는 useInfinityQuery 도 있고 굉장히 여러가지 용도로 사용할 수 있는 함수들이 많이 있는 것 같습니다.

결론

이처럼 React Query 를 잘 사용하여 Server State 관리 로직을 store에 분리시킬 수 있다면 비대해진 store를 조금 더 가볍게 만들 수 있고, API 콜 로직도 간단히 작성할 수 있을 것 같습니다.
그렇게 되면 추후에 레거시가 될 때에도 유지보수 하기에도 용이해질 것 같습니다.
그러나 React Query가 좋다고 무조건 써야하나? 요즘 개발 트렌드이니 따라가야 하나? 하는가에 대한 질문에는 팀 내 동료들끼리 충분히 고민해보고 React Query를 도입했을때 장점들을 잘 활용할 수 있나, 우리 서비스에 작 접목시킬 수 있나가 중요한 것 같습니다.
실시간으로 변하는 코인 시세를 보여줄 필요가 없는 페이지나, 신규 프로젝트에서는 한번 상황에 맞게 적절히 기술을 도입하면 좋을 것 같습니다.

저작자표시 (새창열림)

'Frontend' 카테고리의 다른 글

나는 웹 성능 지표를 잘 알고 있었나?  (0) 2023.06.04
그래서 모노레포가 뭐지? (feat. yarn workspace)  (0) 2023.04.09
모듈 시스템의 역사와 모듈 번들러 알아보기  (0) 2023.03.12
CSS 스펙 탐구하기  (0) 2023.03.08
React에서 babel의 동작원리 & React와 Virtual DOM 이해하기  (0) 2022.12.13
    'Frontend' 카테고리의 다른 글
    • 그래서 모노레포가 뭐지? (feat. yarn workspace)
    • 모듈 시스템의 역사와 모듈 번들러 알아보기
    • CSS 스펙 탐구하기
    • React에서 babel의 동작원리 & React와 Virtual DOM 이해하기
    devdubby
    devdubby

    티스토리툴바