Why use React Query?

Sally·2022년 10월 26일
1
post-thumbnail

공식 팀의 경우 상태 관리 툴 중 하나로 리액트 쿼리를 채택하였다. 해당 이유는 나의 개인적인 경험과 이전에 사용해본 리덕스와의 비교를 통해서 나온 결론이였다. 리덕스도 많은 장점이 존재하지만, 우테코의 4개월이라는 짧은 시간 동안 제한된 인원으로(프론트 엔드 2명) 개발해야한다는 제약이 있었기에 아래에 나열된 이유들로 리액트 쿼리를 채택하게 되었다.

리액트 쿼리의 장점

불필요한 코드 작성을 줄여준다

레벨 3 때 부터 시작되는 팀 프로젝트에 앞서 레벨 2에서 리덕스를 사용할 기회가 있었다.
그때에 서버의 비동기 상태 중 팬딩, 성공, 실패 해당 3가지에 따라서 다른 화면을 보여주어야 했는데 리덕스의 경우 해당 상태에 대해서 각각 상태를 정의 해주는데에 상당한 코드를 작성해야했다.

예를 들어 장바구니 정보를 서버로 부터 받아오는 동안은 로딩 UI를 보여주고 성공 상태일 때에는 isLoading을 false로 하여 로딩 화면을 보여주는 것을 멈추고 응답값을 컴포넌트에서 사용할 수 있는 기능을 리덕스로 구현해본다고 생각해보자
(아래는 실제, 장바구니 미션에서 작성한 코드이다)

  1. 상태의 초기값을 PENDING으로 설정하고 dispatch를 통해 스피너를 보여주도록 명령한다.
  2. 액션 함수에서 서버에서 응답값으로 200이 나왔을 경우 상태를 SUCCESS로 실패 했을 경우에는 FAILURE로 정의한다.
  3. dispatch를 통해서 각각의 상태에 대해서 상태 값과 성공일 때의 필요한 액션들을 payload에 담아 보낸다.
  4. 리듀서 함수에서 action을 통해 넘어온 상태값과 payload를 통하여 상태값을 재정의 한다.
  5. 사용하는 컴포넌트에서는 에서의 useSelector를 통해 응답값을 가져와 활용한다.

이렇듯, 리덕스의 경우 하나의 명령을 처리하는데에 액션함수, 리듀서, 초가 상태 값을 정의하는 코드 등 많은 양의 코드가 필요하다.

하지만, 리액트 쿼리의 경우 Connectors, Action Creators, Middleware, Reducers, Loading/Error/Result States 등 의 비동기 코드를 처리하기 위해 부가적으로 필요한 코드들을 작성할 필요가 없어져 리덕스에 비해 코드 양이 상당히 줄게 된다.

또한 onSuccess나 onError와 같은 옵션들로 비동기 통신이 성공했을 때에 어떤 액션을 취할 것인지 바로 넣어줄 수 있어, 다른 이가 코드를 볼 때에 해당 코드들을 성공, 실패일 때에 어떤 액션을 취할 것인지 파악하기가 쉬워진다.

이러한 줄어든 코드 작성이 주는 장점은 개발자 2명과 4개월이라는 제약사항을 가진 공식에게는 매력적으로 보여졌다.

custom hook 분리를 통한 상태 관리

리액트 쿼리의 경우 서버 상태와 클라이언트 상태를 분리할 수 있다.

*서버 상태, 클라이언트 상태란? 🤔

  • server state: 서버로 부터 불러오는 데이터를 의미
    클라이언트가 제어, 소유할 수 없기 때문에 서버로 부터 특정 시점의 데이터를 가져와 저장하여 사용한다. 이 때문에 비동기적인 상태를 갖는다.
  • client state: 폼 입력, ui 테마, 사이드 바 상태 등 클라이언트가 제어, 소유하는 데이터를 의미한다. 이 때문에 동기적인 상태를 갖는다. client state는 또 다시 2가지의 상태로 나누어진다.
    1. local client state: form입력, 사이드바 상태 등 하나 또는 인접한 컴포넌트들에서 이용되는 state
    1. global client state: ui 테마와 같이 여러 곳에서 사용되는 state

서버 상태를 관리하는 코드들을 리액트 쿼리가 모두 가져가면서, hook을 통해 컴포넌트에서 분리가 가능했다.

실제로, 공식에서도 리액트 쿼리를 통해 게시글을 가져오는 로직을 분리하고 컴포넌트에는 서버의 응답 값 데이터을 넘겨주는 형식을 취할 수 있었다.

캐시를 통한 데이터를 valid하게 관리하기

다음으로는 리액트 쿼리는 캐시 설정이 편리하다. cache time과 stale time 설정을 통해서 각각의 서비스에 맞게 서버와의 데이터 점검 시간을 조절하고 불필요한 api 호출을 줄일 수 있다.

리액트 쿼리의 경우 'stale-while-revalidate'캐싱 전략을 활용하여 캐시를 관리한다.

데이터를 캐싱하고 해당 데이터가 필요할 때 더이상 stale(=최신 상태가 아닌 데이터) 데이터라도 제공한다.
이는 리액트 쿼리가 서버와의 응답을 받아오는 동안 유저에게 Loading Spinner를 표시하는 것 보다 오래된 데이터를 보여주는 쪽을 택했기 때문이다. 그리고 오래된 데이터를 보여주는 동안, 동시에 background re-fetch를 수행하여 해당 데이터를 다시 검증하게 된다.

즉, cache time이 만료 되었을 때에는 즉시 리액트 쿼리가 서버와의 통신을 통해서 데이터를 받아와 화면을 재렌더링 시키지만 stale time이 넘어간 시점에서는 서버에서 캐시된 데이터가 유효한지 검증하는 동안 cache 된 데이터를 보여주게 된다.

*cache time과 stale time 어떤 점이 다른 걸까?🤔

  • stale time : query가 fresh에서 stale로 전환될 때 까지의 유효 기간, 최신 쿼리라면 데이터는 항상 캐시에서만 가져오고 네트워크 요청은 하지 않는다. stale time이 지나간 경우 캐시된 데이터를 가져오긴 하지만 cache time이 만료되거나 쿼리 함수를 실행하였을 때에 등의 경우에 백그라운드에서 refetch가 될 수 있다.
  • cache time: 비활성 쿼리가 캐시에서 제거될 때 까지의 기간 쿼리는 등록된 관찰자가 없는 즉시 비활성 상태로 전환되므로 해당 쿼리를 사용하는 모든 구성요소가 unmounted된다

리액트 쿼리의 default Cache time은 5분 stale time의 경우 0으로 설정되어 있는 데 해당 옵션을 공식 프로젝트 내에서는 변경하지 않았다.

이유로는, 유저에게 보여주는 정보가 정확하길 바랬기 때문이다.
stale time을 0으로 하게 되면 cache time이 5분이기 때문에 미리 캐시된 값을 유저에게 보여줄 수 있지만, 백그라운드에서는 캐시된 값이 유효하지 않기 때문에 다시 비동기 요청을 해오게 된다. 이렇듯 리액트 쿼리를 유저에게 기다림 없이 변동 된 데이터들을 보여줄 수 있기 때문이다. 그리고 하나의 글을 작성할 때에 짧은 글이라면 5분 정도 걸리지 않을까? 라는 생각과 함께 5분이 지나면 무조건 새로운 값을 요청하여 유저에게 보여주도록 하였다.

다만,인기 게시글, 게시글 목록 등을 보여주는 컴포넌트에서는 refetchOnWindowFocus 옵션의 경우 False를 주었다.
이유는, 크롬 배경을 실수로 클릭 하는 것 만으로 요청이 가는 것은 아직 사용하는 유저가 많지 않아 데이터 변동이 적은 우리의 서비스에서는 과도한 옵션이라고 생각했기 때문이다.

refetch , invalidateQueries가 용이하다

앞서 설명된 장점과 관련되어, refetch가 유용하다는 것도 리액트 쿼리를 선택한 이유 중 하나였다.

공식 서비스의 경우 게시글, 댓글을 작성, 수정, 삭제가 가능하도록 해야했다. 유저가 게시글 또는 댓글을 수정하였을 때에 바로 이전의 게시글에 대한 캐시를 날리고 수정된 데이터를 다시 서버에서 받아와 보여주어야 했다.(그렇지 않으면 수정되지 않은 정보가 유저에게 보여져야하기 때문이다)

이 때에 react query에서 제공하는 refetch 와 refetchQueries를 활용하여 수정, 삭제 요청이 성공하였을 때에 따로 캐시 제거 하는 코드나 새로운 수정된 게시글에 대한 값을 요청 하는 코드를 짤 필요 없이, 리액트 쿼리가 다시 게시글, 댓글에 대한 get 요청을 서버에 보내 서버 상태를 업데이트 해준다.

또한 무한스크롤을 구현할 때에도 refetch를 활용하여 유저가 게시글을 끝까지 본 경우 서버에서 get요청을 더 받아오도록 쉽게 구현할 수 있었다.

추가적으로 QueryClient와 key값을 통해서 쉽게 데이터들을 refetch하여 변동된 데이터에 따라 달라진 정보를 유저에게 보여주도록 하였다. 이러한 점들이 여러 서버 상태들을 관리하기 편리하게 해 주었다.
(공식 팀에서 예를 들면 투표를 수정을 성공하였을 때에 다른 코드 단에 위치한 투표 득표수에 대한 정보들을 다시 받아와 보여주었다.)

리액트 쿼리에서 제공하는 다양한 옵션

그 외에도 리액트 쿼리에서는 다양한 옵션들을 제공해준다.

  • 선언적인 상태 관리 :
    isError, isSuccess, onSuccess등이 react-query에서 선언적으로 구현되어 있고, 이를 통해 데이터 통신이 성공했을 때, 로딩 중일 때에 따라 다른 UI를 보여주는 것이 편리하다
  • 무한스크롤을 위한 useInfiniteQuery 제공:
    hasNextPage와 fetchNextPage를 통해서 무한스크롤을 쉽게 구현할 수 있다

참고 문헌

https://tanstack.com/query/v4/docs/guides/does-this-replace-client-state?from=reactQueryV3&original=https://react-query-v3.tanstack.com/guides/does-this-replace-client-state
https://techblog.woowahan.com/6339/
https://tech.kakao.com/2022/06/13/react-query/
https://tech.toktokhan.dev/2021/04/23/react-state-management/
https://parang.gatsbyjs.io/react/2022-react-11/

0개의 댓글