React-Query 소개, 도입 후기

fethpiao·2023년 7월 30일
0
post-custom-banner

리액트쿼리 공식 홈페이지

필자 상황 소개

오픈마켓 프로젝트를 시작하며 기존에 사용해왔던 Vue2 대신 React로 전환했습니다. 이때 새로히 react로 전환하면서 프로젝트에 새로 도입할 라이브러리를 찾아보게 되었습니다. 현재는 약 1년이 지난 시점으로, 리액트쿼리 도입 및 회고에 관한 글을 작성하려고 합니다.

React-query 소개

리액트 쿼리는 서버 상태관리 라이브러리로 api로 가져온 데이터를 효율적으로 관리할 수 있습니다. 결과조회, 로딩 여부, 에러 여부, 캐싱, 재호출, 그리고 디버깅을 위한 시각화 툴(ReactQueryDevtools)을 제공합니다.

리액트 쿼리로 해결할 수 있는 것

  1. 데이터를 키값으로 조회 및 관리할 수 있는 캐시 데이터로 관리하여 중복요청을 제거
  2. api함수를 통해 받아온 데이터의 로딩상태, 오류 관리
  3. 캐시 데이터 최신화
  4. 프리페칭 (필요한 데이터를 원하는 시점에 미리 호출 할 수 있음)
  5. 페이징, 무한스크롤 (useInfiniteQuery)
  6. 오류나 성공 시 미리 정의한 콜백함수를 실행할 수 있음
  7. 오류에 대한 재시도, 캐시 상태에 따른 데이터 최신화(api 재호출), 일정 시간 간격으로 데이터 최신화 등등

React-query 도입 결정

위와 같은 기능 지원과 npm weekly download 100만이 넘는 인기와 커뮤니티, 구글링시 쉽게 찾아볼 수 있는 여러 사용예시는 react-query가 서버상태관리 라이브러리와 리액트 개발의 큰 축이 된 듯 보였습니다. 기능 소개 및 프로젝트에 일부 도입하여 사용예시를 보여주는 자리를 가지고 난 후의 팀장님과 팀원들의 의견은 '도입하지 않을 이유가 없다' 였습니다.

프로젝트에 도입하며 유용했던 점

초반에 리액트 쿼리를 도입하고 나고 8개월이 지난 후, 오픈마켓 서비스 및 관리자 사이트를 오픈했습니다. 서비스 페이지와 관리자 싸이트의 페이지들의 수만 150여개를 훌쩍 넘겼고, 그 페이지들 내에서 사용하는 수많은 컴포넌트와 기능들을 구현해가면서 리액트 쿼리에서 제공하는 거의 모든 기능을 사용하면서 시행착오를 겪었습니다. 프로젝트를 회고하며 react-query에 대해 느낀 점은 도입을 결정하면서 느꼈던 점과 같이 '도입하지 않을 이유가 없다' 입니다. 실제 적용하면서 겪었던 수많은 사례중에 특히나 좋았던 점을 몇가지 정리해보겠습니다.

1. 상태를 가지고 있는 캐시된 데이터.

개인적으로 생각하는 리액트 쿼리의 가장 강력한 기능이라고 생각합니다.
api를 통해 가져온 데이터는 최신화 문제를 늘 달고 있습니다. 자주 업데이트 되지 않는 데이터를 로컬 페이지에서 불러오면서 불필요하게 자주 fetch할 수도 있고, 이를 해결하기 위해 전역상태에 보관한다면 데이터가 지나치게 오래 업데이트 되지 않을 수 있습니다.

이 문제를 리액트 쿼리에서는 데이터를 키값과 함께 상태로 관리하는 것으로 해결합니다. 전역적으로 혹은 데이터키마다 staleTime, cahshTime을 설정하여 일정 시간 동안은 fresh한 데이터인지를 알 수 가 있으며, 설정한 시간이 지난 후에는 자동으로 다시 조회하여 데이터를 최신화 할 수 있습니다.

또한 이 시간 안의 데이터라 할지라도 수정, 삭제가 일어날 시에 키값을 통해 데이터에 접근하여

'이 데이터는 더이상 최신 데이터가 아니니 무효화 해줘'  

혹은

'데이터의 특정 데이터만 업데이트 할게. 나중에 문제가 생기면 원상태로 복구할꺼야'

와 같은 작업을 처리 할 수 있습니다. 또한 별다른 기능 구현 없이 일정한 일정한 시간 마다 자동으로 데이터를 최신화하게 할 수 도 있습니다. 이러한 기능은 전역상태 라이브러리를 사용하는 듯한 효과를 가지면서도, 최신화 이슈에 대한 충분한 대응책을 갖게 됩니다.

2. 비지니스 커스텀 hook 작성의 용이함

추가로 캐시된 데이터라는 특징 덕분에, 커스텀 비지니스 hook을 작성할 때 특히 유용합니다. hook안에 데이터를 조회하는 부분, 등록 수정 삭제하는 부분을 같이 모아두면 사용하려는 쿼리 데이터와 관련 함수들을 캡슐화 할 수 있습니다. 등록,수정,삭제와 같은 경우 useMutation이라는 훅을 사용하게 되는데, 이 훅에 성공, 실패시 콜백 함수를 넘겨줄 수 있습니다.
useMutation 함수의 onSuccess(성공시 실행 콜백), onError(실패시 실행 콜백), onSettled(성공, 실패시 콜백) 함수를 사용하면, 수정 성공시에 query key로 목록이나 상세 조회 데이터를 무효화 시키는 작업을 하게하는 식으로 사용할 수 있습니다.

ex)
const useProduct = () => {

const queryClient = useQueryClient()

// 상세 조회
const {{data: {product}} = {} } = useQeury(['상품', 일련번호], () => 조회api(일련번호))

// 수정
const {mutate: mutateModify} = useMutation(modifyApi, {
	onSuccess: () => {
   	// 상세 데이터 수정 성공시 목록 데이터 최신화 
       void queryClient.invalidateQueries(['상품'])
       // 혹은
       void queryClient.setQueryData(['상품'], 업데이트함수)
       
   	// 상세 데이터 수정 성공 시 상세 데이터 최신화
      void queryClient.invalidateQueries(['상품', 일련번호])
      Toast.success('수정되었습니다.')
   }
   })
  
  return {product, modify: mutateModify }
}

3. 각종 전역 처리

리액트 쿼리는 useIsFetching, useIsMutating과 같은 다른 유용한 hook들을 제공합니다. 이는 현재 데이터를 조회해오는 중인지, 지금 데이터 조작 api가 실행중인지 등을 알 수 있는 hook입니다. 이를 통해 각 페이지에서 개별적으로 로딩을 표시하지 않고, App 컴포넌트에서 로딩을 표시하는 식으로 처리하기에 용이합니다.

ex)
const isMutating = useIsMutating()
{isMutating ? <LoadingSpinner /> : null}

추가로 useMutation 훅은 캐시 데이터가 없기 때문에 기본적으로 쿼리키를 사용하지 않지만, 특정 useMutation에는 mutationKey를 부여할 수도 있습니다. 이를 사용하면 전역에서 수정작업에 로딩바를 표시해주더라도, 특정한 작업시에는 로딩바를 표시하지 않는 식으로 활용할 수 있습니다.

	ex)
  // 로딩스피너 제외 mutation
  const isLoadingExceptMutating = useIsMutating({
    mutationKey: [MUTATION_KEY.로딩제외]
  })

4. 로딩상태 처리 (feat. suspense)

useQuery 함수의 리턴값으로는 data외에 isLoading, isFetching, isSuccess 등의 데이터 펫칭 상태에 대한 정보를 리턴해주고 있습니다. 이를 활용하면 화면에서의 로딩바 표시가 수월해집니다.

const {data, isLoading, isSuccess} = useQuery()

필자는 이것 외에도 react-query에서 제공하는 suspense 옵션을 활용해주었습니다.

// 전역 설정
// react-query 전역 설정
export const queryClient = new QueryClient({
  defaultOptions: {
  	queries: {
    	suspense: true
    }
  }
})

// 개별 설정
useQuery([쿼리키], 쿼리함수, {
	suspense: true
})

React-query의 서스펜스 옵션을 사용하면, 데이터가 로딩되는 동안 해당 query를 사용하는 컴포넌트는 suspense 상태를 가지게 되고(, fallback, ErrorBoundary 래핑을 해줘야 합니다.), 로딩이 완료된 시점에 컴포넌트가 렌더링되므로, useQuery가 리턴해주는 isLoading, isSuccess 와 같은 상태에서 좀더 자유로워 질 수 있습니다. 하지만 suspense 옵션은 query를 사용하는 컴포넌트가 통으로 묶여버리기 때문에 구현 용이성이나 요구사항에 맞게 사용여부를 결정하는 것을 추천드립니다.

profile
웹프로그래머
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 7월 30일

이런 유용한 정보를 나눠주셔서 감사합니다.

답글 달기