pile·
프론트엔드·트렌비트렌비·

찜으로 찜해보는 react-query

트렌비 프론트엔드가 리덕스 기반 상태 관리의 불편함을 정리하고 react-query를 점진 도입한 기록이다. 찜 기능을 예로 useQuery와 useMutation을 붙이고, 캐시가 서버 상태와 어긋나는 두 상황을 각각 다른 방법으로 푼다. 리덕스로 같은 기능을 만든 코드를 나란히 붙여 분량 차이를 보여준다.

핵심 포인트
  • API 하나를 화면에 그리려면 액션, 제너레이터, 성공·실패 액션, 스토어, 셀렉터까지 만들어야 했다.
  • 컨테이너별로 사가와 스토어가 묶여 있어 다른 컨테이너의 API를 부르면 종속성이 생겼다.
  • 리덕스를 걷어내지 않고 얹을 수 있다는 점 때문에 react-query와 SWR을 최종 후보로 뒀다.
  • SWR이 3분의 1 크기지만 교체 사유는 크기보다 기능 부재일 확률이 높다고 보고 react-query를 골랐다.
  • staleTime은 값이 유효한 기간이고 cacheTime은 값을 보관하는 기간이라 역할이 다르다.
  • 찜 개수는 캐시를 직접 갱신하고, 찜 목록은 무효화만 걸어 진입 시점에 다시 받게 나눠 처리했다.
상세 정리
  • 배경: 보일러플레이트에서 온 리덕스를 그대로 써왔고 큰 문제는 없었지만 세 가지 불편이 쌓였다.
  • 불편 1: 캐시가 필요하면 직접 구현해야 했고 파일 다섯 개를 오가야 화면에 값이 떴다.
  • 불편 2: 매거진 목차를 홈에서도 불러야 하는 식으로 컨테이너 간 호출이 늘면서 불필요한 종속성이 생겼다.
  • 불편 3: 리덕스를 쓴다는 것만으로 상태를 관리한다고 할 수 있는지 근본적인 의문이 나왔다.
  • 후보 선정: 리덕스 툴킷과 Recoil, MobX도 봤지만 기존 리덕스를 수정하지 않고 도입할 수 있다는 점에서 react-query와 SWR로 좁혔다.
  • 비교 결과: react-query는 뮤테이션 전용 함수와 디버깅 도구, 캐시 시간 설정, 더 많은 레퍼런스가 강점이고 SWR은 크기가 강점이었다.
  • 관리 대상의 구분: react-query는 서버 상태에 초점을 두며, 모달 열림 여부나 스크롤 위치 같은 클라이언트 상태와는 영역이 다르다.
  • useQuery 감싸기: 훅을 컴포넌트에서 바로 쓰지 않고 함수로 한 번 감싸 API가 특정 컴포넌트에 종속되지 않게 했다.
  • 키 설계: 캐시와 수정·삭제의 기준이 되므로 중복되면 안 되고, 배열로 주면 타입 같은 값을 덧붙여 세분화할 수 있다.
  • 키의 이점: 상위 키만으로 하위의 모든 조합에 접근할 수도 있고, 조합 전체를 지정해 특정 데이터만 다룰 수도 있다.
  • 초기값: 옵션의 initialData로 undefined 대신 0 같은 값을 주면 첫 렌더링에서 방어 코드를 줄일 수 있다.
  • 기본값 함정: cacheTime 기본이 5분, staleTime 기본이 0이라 훅을 쓸 때마다 API가 호출된다.
  • 두 시간의 차이: staleTime이 지나도 cacheTime 동안은 캐시에 남아 있고, 반대로 캐시가 없으면 staleTime이 남아 있어도 다시 호출한다.
  • 조합별 동작: 세 가지 극단적인 설정으로 두 컴포넌트가 차례로 호출할 때 무엇이 반환되고 언제 API가 나가는지 짚는다.
  • useMutation: 읽기를 뺀 나머지에 쓰며 캐시를 쓰지 않아 키나 시간 설정이 없다. 훅을 부르는 시점이 아니라 mutate를 부르는 시점에 요청이 나간다.
  • 어긋남 1: 찜을 추가해도 개수는 캐시된 값이라 staleTime 동안 그대로였다.
  • 어긋남 1의 해법: onSuccess에서 queryClient로 해당 키의 캐시를 읽어 개수를 더한 값으로 다시 세팅해 추가 API 호출 없이 반영했다.
  • 어긋남 2: 찜 목록 페이지에 가면 방금 담은 상품이 안 보였다.
  • 어긋남 2의 해법: 헤더의 개수와 달리 목록은 즉시 반영될 필요가 없으므로 invalidateQueries로 무효화만 걸어 진입 시점에 다시 받게 했다.
  • 결과 1: 리덕스에서 파일 다섯 개를 만들던 작업이 두 개로 줄어 구현과 디버깅이 단순해졌다.
  • 결과 2: 여러 페이지에서 자주 쓰이는 찜과 상품 영역을 전환하며 중복 코드 약 3,000줄을 제거했다.
  • 결과 3: 서버 상태는 react-query가, 클라이언트 상태는 리덕스가 맡게 되면서 리덕스 의존이 낮아지고 장기적으로 다른 도구로 옮길 계획도 세울 수 있게 됐다.
왜 읽나리덕스로 서버 데이터까지 관리하다 보일러플레이트에 지쳤다면, 걷어내지 않고 얹는 전환 경로와 mutation 이후 캐시를 맞추는 두 갈래를 그대로 참고할 수 있다.
트렌비
트렌비 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2