Conference/if(kakao)

[if(kakao)2022] 눈에 보이지 않는 개선: My구독의 Redux에서 React-Query 전환 경험 공유

Lamue 2023. 11. 23. 11:15

자바스크립트에서 HTTP Requests를 위한 방법에는 ajax, fetch, axios 등의 방법이 있습니다. 그 중 axios는 자바스크립트 어플리케이션에서 서버와 통신할 때(=데이터를 가져올 때) 사용되는 대표적인 HTTP 비동기 통신 라이브러리입니다.

현대 웹 환경에서 서버에서 데이터를 받아오는 일은 axios를 기반으로 이루어지고 있습니다.

 

React-Query는  리액트 애플리케이션에서 서버 상태 가져오기, 캐싱, 동기화 및 업데이트를 보다 쉽게 다룰 수 있도록 도와주며 클라이언트 상태와 서버 상태를 명확히 구분하기 위해서 만들어진 라이브러리입니다. 자세한 장단점은 향후 Ajax - Fetch API - Axios - React Query를 정리하는 포스팅으로 소개드릴 예정입니다. 

 

React-Query와 관련된 학습 레퍼런스를 찾던 중에 작년에 카카오에서 주관하여 진행된 if(kakao) Dev 2022 발표에 대해 접하게 되었습니다. if(kakao) 2022 발표에선 실제 실무에서 '왜' 해당 기술을 사용하고 있는지, '왜' 기술 스택을 전환하게 되었는지, '어떻게' 해당 기술을 사용하고 있는지에 대해 실무 경험과 함께 발표를 구성하고 있습니다. 그 중 몇 가지 발표는 정리하면 좋겠다고 생각을 했고, 정리한 내용을 공유할 수 있으면 더 좋을 거 같아서 이렇게 포스팅 시리즈를 기획하게 되었습니다. 본 포스팅을 시작으로 if(kakao) Dev 2022 발표들 중 일부를 정리하여 전달할 예정입니다.

 

포스팅은 if(kakao) 2022 발표 중 [눈에 보이지 않는 개선: My구독의 Redux에서 React-Query 전환 경험 공유] 정리하여 작성하였습니다. 해당 발표는 React 개발 경험이 있으며, Redux 사용 경험이 있는 청자를 대상으로 진행되었습니다. 

 

 

Introduction

카카오 My 구독 팀에선 Redux에서 React-Query로 ‘보이지 않는 개선’을 진행하였습니다.

기존의 설계에 맞춰 개발을 진행하다 보면 사용자 경험 개선 작업과 새로운 기능을 추가해야 하는 상황이 생깁니다. 그러다 보면 기존의 설계에서 벗어나 당장에 필요한 기능을 추가해야 하는 일이 발생합니다. 이로 인해 기존의 설계에서 벗어나 개발을 해야하는 상황이 벗어나고 전체 구조가 바뀌게 됩니다.

이처럼 완벽한 설계는 개발에 있어서 있을 수 없습니다. 

이러한 상황이 누적되고 성능 개선에 대해 고민해봐야 하는 시점이 오면 리팩토링 계획을 세우고 기존의 설계를 토대로 새로운 설계를 진행합니다. 

My구독팀에선 담당하고 있는 기능을 정리하면톡서랍 플러스이모티콘 플러스기능으로 나눌 있습니다.

하지만 기존의 설계에선 고려하지 못했던 인앱 결제, 이용권, 구독 변경 등의 기능을 추가하게 되면서 성능적인 측면에서 개선의 필요성을 느끼게 되었고, Redux에서 React-Query로의 전환을 진행하게 되었습니다. 

 

 

React-Query 조금 알아보기

React-Query 다음과 같은 기능을 제공하고 있습니다.

그렇다면 React-Query 없이 React 자체만으로는 위의 기능을 구현할 없을까요? 

 

React에서 제공하는 기본 Hook으로도 충분히 멋진 비동기 처리 커스텀 훅을 만들 수 있습니다. 

또한 필요한 기능만을 구현할 수 있으며 별도의 라이브러리 설치가 필요 없습니다. 

하지만 기본 Hook 만들기 위해서는 시간과 노력이 필요합니다. 또한 React-Query 대비 안정성을 확보하기 어렵습니다.

이러한 측면에서 FE 개발자 커뮤니티에서 현재 가장 사랑받고 있는 React-Query 대한 도입을 고려해보는 것은 상당히 매력적으로 다가왔습니다.

 

 

Redux에서 React-Query로 전환 이유

Redux에서 React-Query 전환을 결정하게 이유를 React 구조, 데이터의 구분, 에러 처리 항목으로 나누어 설명하겠습니다.

먼저 Redux의 구조입니다.

Redux 본래 비동기 처리를 처리하기 위해 등장한 라이브러리가 아닙니다. 따라서 Redux에서 비동기 처리를 하기 위해선 Redux-Saga 같은 별도의 미들웨어를 사용해야 합니다.

My 구독 팀에서는 isLoading, isSuccess, isError등의 상태를 사용한 기능을 구현하기 위해 loadStatus 만들어 별도의 상태를 관리하였습니다.

하지만 React-Query에선 앞서 언급한 isLoading, isSuccess, isError 같은 상태를 기본 옵션으로 제공하고 있습니다. 

Redux-Saga 사용하여 API 관리할 때에는 Saga들이 서로 Nested 구조에 의해 변경 작업을 진행한 곳에서 멀리 떨어진 위치에서 Side Effect 발생하여 이를 처리해야 하는 일이 발생할 있습니다.

React-Query에선 API Query 통해 작업을 진행하여 이러한 Side Effect 문제를 해결할 있습니다.

다음은 데이터 구분입니다.

My 구독의 경우 대부분의 데이터가 서버 사이드 데이터입니다. 클라이언트 사이드 데이터는 많지 않을 뿐더라 내부적으로 그렇게 복잡하지도 않습니다. 

Redux 사용할 경우 다음과 같이 서버 사이드 데이터와 클라이언트 사이드 데이터를 Redux 하나에서 관리를 해야합니다. 

하지만 React-Query 도입할 경우 다음과 같이 클라이언트 사이드 데이터는 Context API, 서버 사이드 데이터는 React-Query 관리할 있습니다. 또한 클라이언트 사이드의 loadStatus React-Query 기능으로 해결이 가능하여 클라이언트 사이드의 loadStatus 데이터도 필요없게 됩니다.

다음은 에러 처리입니다.

Redux에서의 에러 핸들링은 loadStatus 상태를 활용하여 진행됩니다. API 사용 에러가 발생하면 loadStatus 상태를 변경시켜 리렌더링을 발생시킵니다. 리렌더링을 통해 컴포넌트를 변화시키고 이를 통해 onError 실행시키는 구조를 채택하고 있습니다. 

하지만 React-Query에서의 에러 핸들링은 에러가 발생할 경우 React-Query가 제공하는 기능을 활용하여 바로 onError를 실행할 수 있습니다.

 

그렇다면 다른 라이브러리 대신 React-Query를 사용하는 이유가 있을까요 ?

React-Query 유사한 라이브러리 중에 다음과 같은 훌륭한 라이브러리가 있고, My 구독팀에서도 사용을 검토하였습니다. 

하지만 해당 라이브러리들에 대해 다음과 같은 불편 사항을 확인하였습니다. 

1. RTK-Query: Redux 구조를 그대로 사용해야합니다.

2. Apollo: 추가적으로 스키마를 정의해야합니다. 

3. SWR: React-Query과 거의 모든 측면에서 유사하나 enabled 옵션을 제공하지 않습니다. My 구독 팀에선 enabled 옵션 사용을 고려하고 있었기 때문에 선택에서 제외했습니다.

 

다른 라이브러리 대신 React-Query 사용하는 이유를 정리하면 다음과 같습니다.

  1. Redux에서 API 상태에 따라 화면을 구성하기 위해서는 별도의 도구나 상태가 필요합니다. My 구독 팀에선 loadStatus 이용해 이를 관리하고 있었습니다.
  2. Redux-Saga 의존성이 깊은 구조를 만들어 있습니다. 
  3. Redux 간단한 API 추가 시에도 복잡한 Boilerplate 필요합니다.
  4. Redux API 에러 핸들링 과정에서 불필요한 작업 과정이 발생할 있습니다. 
  5. 물론 React-Query 이외에도 우수한 비동기 처리 라이브러리가 많이 있습니다. 대표적인 예로 RTK-Query, Apollo, SWR 등이 있습니다.
  6. 하지만 사용하는 방식, 구조, 기능에서 React-Qeury 적합하다고 판단하여 React-Query 선택하였습니다.

 

React-Query로 전환 과정

My 구독 팀에서 React-Qeury로 전환하는 과정을 정리하면 다음과 같이 나타낼 수 있습니다.

먼저 Redux 고차 컴포넌트를 Redux Hook으로 전환하였습니다. 다음으로 Redux Hook React-Query 전환하였습니다. 마지막으로 기존에 사용하던 React 16에서 React 18 환경에 맞춰 변경을 진행하였습니다.

컨퍼런스 발표를 통해 실제 My 구독 팀에서 어떤 방식으로 코드를 변경하여 React-Query로 전환하였는지에 대해 구체적으로 다루고 있습니다. 하지만 이에 대해 하나씩 설명드리는건 불필요하다고 판단하여 설명은 생략하였습니다. My 구독팀의 React-Query로 전환 과정이 궁금하신 분들은 [눈에 보이지 않는 개선: My구독의 Redux에서 React-Query 전환 경험 공유]를 통해 확인해보시면 좋을 거 같습니다. 

 

React-Query로 전환 과정에서 My 구독팀은 아래 사항들에 대한 규칙을 세우기 위한 내부적인 노력이 있었습니다. 컨퍼런스 발표에선 시간 여건상 이 중 ‘Query 작성 방식’과 ‘폴더 구조 개선’을 중심적으로 다뤘습니다. 

 

먼저 Query 작성 방식입니다.

기존의 My 구독팀의 Query 작성 방식은 다음과 같이 각각의 API에서 필요한 컴포넌트를 호출하여 사용하는 방식을 사용했습니다.

이러한 기존 구조에서 문제점을 파악하였고, React-Query 도입하기에 앞서 기본 Query API 11로 매칭하여 관리하는 방식을 채택하였습니다.

기본 Query 있는 공통적인 요소를 뽑아서 공통 옵션으로 만들어 함께 관리하고 싶었고, 다음과 같이기본 Query 위한 기본 Query 만들어 사용하였습니다.

이에 대해 구체적으로 알아보겠습니다.

  1. 페이지 단위 Key값을 추가하여 페이지 단위 쿼리 초기화를 가능하게 하였습니다.
  2. Select에서 실질적인 데이터가 있는 값만 반환함으로써 쿼리를 사용할  중복 Select 방지하였습니다.
  3. onError 옵션이 있어야 useErrorBoundary 설정하도록 처리하여 에러 핸들링 누락을 방지하였습니다.

 

다음으로 폴더 구조 개선입니다.

기존에는 데이터의 목적에 따라 파일을 분류하였습니다. data라는 폴더 안에 각각의 api 그룹 폴더를 만들어 사용하였습니다. 

React-Query 사용하게 되면서 현재는 api 요청 형태에 따라 파일을 분류하게 되었습니다. 예를들어 GET 방식으로 요청하는 경우 query 만들어 관리하고, POST DELETE 등의 방식으로 요청하는 경우 mutation으로 만들어 관리하였습니다. 또한 데이터의 목적으로도 분류하기 위해 하위 파일의 네이밍을 데이터의 목적에 따라 분류하게 되었습니다. 

그 결과 My 구독에선 다음과 같은 이점을 얻을 수 있었습니다.

  1. 중복 API  로직을 최소화할  있었습니다.
  2. API 네이밍 지정에 대한 고민도 최소화할  있었습니다.
  3. import 경로에서 목적을 표현할  있었습니다.
  4. 동일한 API 요청을 최소화할  있었습니다.

 

Redux에서 React-Query로 전환 과정을 정리하면 다음과 같습니다.

  1. My 구독팀의 특성과 상황에 맞춰  3단계로 나누어 전환작업을 진행하였습니다. 
  2. Redux Hook 기반으로 전환할  select   최적화하는 방법에 대해 고민했습니다.
  3. React-Query 전환하는 과정에서 React 16 상태지옥에서 벗어나는 방법에 대해 고민했습니다.
  4. My 구독에서 React-Query 어떻게, 어떤 구조로 사용하는지 공유했습니다.
  5. 마지막으로 React 18 전환하면서 어떤 이점이 발생하는지에 대해 고민했습니다.

 

그래서 써보니 어떤가요 ?

My 구독팀에서 React-Query를 사용하면서 경험했던 좋았던 점과 아쉬웠던 점에 대해 소개드리겠습니다. 

 

이런 점에서는 좋았습니다. 😆

  1. Hook 기반을 제공하고 캐싱을 지원합니다. 
  2. 비동기 처리를 위한 유용한 도구를 제공합니다. React-Query에서 제공하는 isLoading, isSuccess, Infinite Queries 등의 기능을 사용하여 효율적으로 코드를 구성할 수 있었습니다.
  3. 백그라운드 패칭을 지원합니다. 
  4. 게시판 댓글, 좋아요 기능, 주식 목록 등의 실시간 서비스를 제공할 때 더욱 효율적으로 서비스를 제공할 수 있을 거 같습니다.

 

이런 상황에서는 고민해보세요. 🤔

  1. 서버 사이드 데이터가 거의 없는 경우엔 사용을 고려해보세요. Recoil, Redux 등을 통해 데이터를 관리하다가 서버 사이드 데이터가 더 많아질 때 적용하는 것을 고려해보시는 것을 추천드리겠습니다.
  2. React 18에서 단순히 비동기 처리 상태 때문에 도입을 고려하는 경우, React 18에서 제공하는 Suspense 등의 기능이 충분한 대안이 될 수 있습니다.

 

이런 아쉬운 점이 있습니다. 😭

  1. Mutation을 한 번만 호출하기 위해 Wrapper 함수가 필요합니다. 
  2. UI 테스트를 진행하게 될 때 Mock API가 필요할 수 있습니다. My 구독 팀에선 Storybook을 이용하여 E2E Test와 Sanpshot Test를 진행하고 있는데, 해당 테스트를 진행하기 위해서 Mock Service Worker라는 Mock API를 추가로 구현해야했습니다. 
  3. 항상 서버 데이터와 같은 데이터를 바라보는 것이 좋은 것은 아닙니다. 예를 들어 1회성 페이지를 렌더링한 경우, 서버에 재요청을 하면 에러가 발생할 수 있습니다. 이를 막기 위해 1회성 API를 구현하고 있습니다. 
  4. 전체적인 데이터 흐름을 파악하기 어렵다는 느낌을 받기도 합니다. 

 

 

마치며

지금까지  if(kakao) 2022 발표 중 [눈에 보이지 않는 개선: My구독의 Redux에서 React-Query 전환 경험 공유]의 발표 내용을 정리하여 전달하였습니다. 

 

항상 라이브러리를 학습할 때 'Why'와 'How'에 집중하여 정리하고자 노력하고 있는데, 현업에선 레거시코드, 업무 방식, 사용 방식에 따라 여러 라이브러리를 검토하고 제공하고 있는 기능을 비교하며 신중하게 사용하고 있다는 점이 인상 깊었습니다. 특히 실무자의 관점에서 라이브러리를 전환하고 마이그레이션 시키는 과정에 대해 알아볼 수 있는 좋은 발표였습니다. 

 

항상 라이브러리에 대해 열심히 공부하고 있으면 멘토님이 '이렇게 좋은 라이브러리인데 왜 모두가 이 라이브러리를 사용하지 않을까요?' 하며 시니어 개발자의 관점에 대해 알려 주시곤 하십니다. 당시에는 '어차피 시간이 지나면 이 라이브러리가 주류를 이루지 않을까?' 라고 생각했지만 실제 실무에선 사용하는 기능, 방식, 그리고 마이그레이션 비용 등 고려해야할 사항이 많다는 것을 다시금 알아볼 수 있었습니다. 

 

 

참고

https://youtu.be/YTDopBR-7Ac?si=t-TvUX0liuzZtKaz