우아한테크세미나: React Query와 상태관리

bruney·2022년 2월 22일
0

세미나

목록 보기
1/1
post-thumbnail

세미나 링크입니다!

세미나를 들은 계기 및 느낀점

평소 Redux를 사용하다가 RTK의 장점을 깨닫고 이후로 진행하는 프로젝트에서는 RTK를 사용했다. RTK를 사용하며 Redux보다는 코드량이 많이 줄었지만 여전히 api요청하는 코드가 대부분이었고 이게 과연 클라이언트 스토어인가?라는 의문점도 있었다. 또한, 여전히 Boilerplate코드가 많아 불편하다는 생각을 하고 있었다. 그러던 도중, react query, swr, RTK query가 무엇을 하는 도구인지 깨달았고 정말 편리하겠다는 생각을 했다. 공부를 하는 학생의 입장에서 당연히 배워야겠다는 생각을 했고 찍먹(?)하기 위해 공식 문서를 살펴봤지만 자세히 들여보기에는 요즘 바쁜 일이 많아 무리라는 생각을 했다. 하지만 마침 배민 주문팀에서 세미나를 해주셔서 경험자의 입장에서 고민과 노하우를 전수받고 싶어 할 일을 제쳐두고 영상을 멈춰가며 필기를 했다.

정말 유익한 시간이었고 세미나를 듣지 않았으면 혼자 고민했을 내용을 3시간 정도를 소모하며 필요한 지식들을 찍먹할 수 있었다. 먼저 토이프로젝트를 가볍게 해보고 현재 진행하고 있는 ESC 팀에서 리액트 쿼리를 도입해야겠다는 생각을 했다. 앞으로도 이러한 세미나는 내 할 일을 제쳐두고서도 꼭 들어야겠다는 생각을 했다.

FE 상태관리란?

상태란?
주어진 시간에 대해 시스템을 나타내는 것으로 언제든지 변경될 수 있음 즉 문자열, 배열, 객체 등의 형태로 응용 프로그램에 저장된 데이터
=> 개발 입장에서는 관리해야하는 데이터들!

모던 FE
UI / UX의 중요성과 함께 프로덕트 규모가 많이 커지고 FE에서 수행하는 역할이 늘어남
=> 관리하는 상태가 많아지며 상태 관리의 필요성이 커졌다.

상태관리는?

  • 상태를 관리하는 방법에 대한 것 -> 프로덕트가 커짐에 따라 어려움도 커진다.
  • 상태들은 시간에 따라 변화한다.
  • React에서는 단방향 바인딩이므로 props drilling 이슈도 존재하여
  • 따라서 리덕스, 몹엑스 등 라이브러리르 활용해 해결하기도 한다.

주문 FE 프로덕트에 대한 고민

배민앱은 모두 웹뷰이다. 따라서 이렇게 많은 웹뷰를 관리하기 위해 상태관리에 많은 고민을 했다.
2021년 여름의 고민

  • 레거시를 청산하고 앞으로를 위한 기틀을 만들자.
  • store에 상태관련 코드보다 API 통신관련 코드가 더 많은거 같은데요..
  • 저희 한 팀 FE에서 관리하는 레포가 몇 개..
  • 3명이서 이 많은 서비스, 레포를 어떻게 관리?..
  • 반복되는 컴포넌트, 로직 또 만드나요?..
    ==> 이왕할거 repo합치고, 레거시도 치우고, 신기술 검토도 하면서 주문 FE 아키텍쳐 통합해보아요!

Redux 상태관리에 대한 고민
store는 전역 상태가 저장되고 관리되는 공간인데 상태관리보단 API 통신 코드..??

여기서 다 관리하는게 맞나..?
상태관리 영역이 서버값을 저장하는데까지 확장

  • API 통신 관련 코드가 모두 Store에?
  • 또, 반복되는 isFetching, isError 등 API 관련 상태
  • 또또, 반복되는 비슷한 구조의 API 통신 코드

서버에서 받아야하는 상태들의 특성

  • client에서 제어하거나 소유되지 않은 원격의 공간에서 관리되고 유지됨.
  • Fetching이나 Updating에 비동기 API가 필요함.
  • 다른 사람들과 공유되는 것으로 사용자가 모르는 사이에 변경될 수 있음.
  • 신경 쓰지 않는다면 잠재적으로 "out of date"가 될 가능성을 지님
    ==> 사실상 FE에서 이 값들이 저장되어있는 state들은 일종의 캐시
    ==> 어쩌면 다른 관리방법이 있다면 좋을지도?

상태를 두가지로 나누어 봅시다.
키포인트는 데이터의 오너십이 있는 곳
Client state

  • Client에서 소유하며 온전히 제어가능함
  • 초기값 설정이나 조작에 제약사항이 없다.
  • 다른 사람들과 공유되지 않으며 client내에서 ui/ux흐름이나 사용자 인터랙션에 따라 변할수 있다.
  • 항상 Client 내에서 최신 상태로 관리된다.

Server state

  • client에서 제어하거나 소유되지 않은 원격의 공간에서 관리되고 유지된다.
  • Fetching/Updating에 비동기 API가 필요하다.
  • 다른 사람들과 공유되는 것으로 사용자가 모르는 사이에 변경될 수 있다.
  • 신경 쓰지 않는다면 잠재적으로 "out of date"가 될 가능성을 지닌다.

=> 지금까지 상태관리 라이브러리가 서버 상태를 관리하기 적합할까? 결론은 React Query!

React Query란?

서버 데이터를 가져오고 캐싱하고 동기화하고 업데이트하는 라이브러리이다. 서버 상태 저장에 용이하다.
또한, zero-config로 즉시 사용가능하다. 원하면 언제든 config도 커스텀 가능하다.
React에서 쓰려면 QueryClientProvider 필수!

세 가지 코어 컨셉 살펴보기

  1. Queries
  2. Mutaions
  3. Query Invalidation

1. Queries

보통 데이터 Fetching용! CRUD 중 Reading용!
query 함수가 많아지면 /queries 파일 분리도 추천!(쿼리 선언부, 컴포넌트에서 쿼리를 사용하는 부분)

Query Key

Key, Value 맵핑구조
Query Key에 따라 query caching을 관리합니다.
String, Array 형태가 있다.

Query Function

Data Fetching할 때 Promise를 반환하는 함수!
데이터 resolve하거나 error를 throw. 즉 API함수를 넣는 부분!

useQuery가 반환하는 것

  1. data: 마지막으로 성공한 resolved된 데이터(response)
  2. error: 에러가 발생했을 때 반환되는 객체
  3. isFetching: request가 in-flight중일 때 true
  4. status, isLoading, isSuccess 등: 모두 현재 query의 상태
  5. refetch: 해당 query refetch하는 함수 제공
  6. remove: 해당 query cache에서 지우는 함수 제공
    etc.
    => 라이브러리에서 제공안하면 사실 우리가 다 구현해야할.. 유용한 여러 인터페이스 제공!

config 커스텀

useQuery 세 번째 인자에서 options를 통해 커스텀할 수 있다.
1. onSuccess, onError, onSettled: query fetching 성공/실패/완료 시 실행할 Side Effect 정의
2. enabled: 자동으로 query를 실행시킬지 말지 여부
3. retry: query 동작 실패 시, 자동으로 retry 할지 결정하는 옵션
4. select: 성공 시 가져온 data를 가공해서 전달
5. keepPreviousData: 새롭게 fetching시 이전 데이터 유지 여부
6. refetchInterval: 주기적으로 refetch할지 결정하는 옵션
etc.

1. queries 질문

Q. useQuery를 이용해 불러온 server state를 이용해 액션을 생성해야 하는 경우가 있을텐데, 이때는 어느 단계에서 server state를 저장하나요?
A. 본래 리액트 쿼리를 사용하는 목적인 서버, 클라이언트 상태의 디펜던시를 없애는 목적에 위배될 수 있지만 어쩔 수 없을 경우, onSuccess에서 작업하면 된다.

2. Mutaions

create, update, delete에서 사용한다.
useQuery 보다 더 심플하게 Promise 반환 함수만 있어도 된다. 단, Query Key 넣어주면 devtools에서 볼 수 있다.

useMutaion

  • mutate: mutation을 실행하는 함수
  • mutateAsync: mutate와 비슷 but Promise반환
  • reset: mutation 내부 상태 clean
  • 나머지는 useQuery와 비슷

options

  • onMutate: 본격적인 Mutation 동작 전에 먼저 동작하는 함수, Optimistic update 적용할 때 유용(좋아요 기능: 유저가 좋아요를 눌렀을 때, 낙관적으로 좋아요를 눌렀다고 생각하고 먼저 UI를 변경, 만약 좋아요를 뺐다면 롤백할 수 있다.)
  • 나머지는 useQuery랑 비슷

3. Query Invalidation

  • 간단히 queryClient를 통해 invalidate메소드를 호출하면 끝!
  • 이러면 해당 key를 가진 query는 stale(신선하지 않은) 취급되고, 현재 렌더링되고 있는 query들은 백그라운드에서 refetch된다.

코어말고 캐싱, 동기화는??

캐싱

RFC 5861을 차용했다고 생각하면 된다.

stale-while-revalidate: 백그라운드에서 stale response(낡은 데이터)를 revalidate하는 동안 로딩 화면을 보여주지 않고 캐시가 가진 stale response(낡은 데이터)를 보여준다.

이러한 컨셉을 메모리 캐시에도 적용해보자!

  • cacheTime: 메모리에 얼마만큼 있을건지
  • staleTime: 얼마의 시간이 흐른 후에 데이터를 stale취급할 것인지

refetchOnMount / refetchOnWindowFocus / refetchOnReconnect
-> true이면 Mount / window focus / reconnect 시점에 data가 stale이라고 판단되면 모두 refetch (예를 들어, 다른 탭으로 갔다가 다시 돌아올 경우)

query 상태흐름

화면에 있다가 없다가 좀 더 복잡한 query

zero-config인 이유는 디폴트 값으로 이런 것들이 세팅되어 있기 때문이다.

  • Queries에서 cached data는 언제나 stale 취급(stale time 설정 가능)
  • 각 시점에서 data가 stale이면 항상 refetch 발생
  • inActive Query들은 5분 뒤 GC에 의해 처리
  • Query 실패 시 3번까지 retry 발생

전역상태처럼 관리되는 데이터들
어떻게 server state들을 전역상태처럼 관리할까요?
-> QueryClient 내부적으로 context 사용

리액트 쿼리 적용 이후

  1. Store는 Client State들만 남아 목적에 맞고 심플하게 바뀌었다.
  2. Server State들은 React Query와 함께 간단하고 파워풀
  3. 컴포넌트는 조금 길어졌지만 수용가능한 범위이다.

리액트 쿼리 장점

  1. 서버상태 관리 용이하고 직관적인 API 호출 코드
  2. API 처리에 관한 각종 인터페이스 및 옵션제공
  3. Client Store가 FE에서 정말로 필요한 전역상태만 남아 Store답게 사용된다.(Boilerplate 코드 매우 감소)
  4. devtools 제공으로 원활한 디버깅
  5. 캐싱 전략 필요할 때 아주 좋다.

고민이 더 필요한 것

  1. 컴포넌트가 상대적으로 비대해지는 문제(컴포넌트 설계/분리에 대한 고민 필요)
  2. 좀 더 난이도가 높아진 프로젝트 설계(컴포넌트 유착 최소화 및 사용처 파악 필요)
  3. 리액트 쿼리의 장점을 더 잘 활용할 방법(단순히 API통신 이상의 가능성)

과연 나도 리액트 쿼리를 쓸 필요가 있을까?

1년 전보다 다운로드 수가 4배 증가 but 트렌드보다 why가 중요하다.

  • 수많은 전역상태가 API통신과 엮여있어 비대해진 Store를 고민하시는 분.
  • API 통신 관련 코드를 보다 간단히 구현하고 싶으신 분.
  • FE에서 데이터 캐싱 전략에 대해 고민하시는 분.
  • 공부가 목적이시라면 모든 FE개발자분들께!

but 언제나 더 나은 방법이 나올 수 있으니 프로덕트를 만드는 우리는 Why를 고민해보자!

질문 리스트

  • useQueries로 처리하면 각 쿼리에 option들이 적용되질 않아서 각 useQuery별로 options 값을 다르게 줘야할 때 parallel한 방식으로 쓰게 되는데, parallel로 선언한 useQuery 부분들의 데이터 패칭이 다 끝날 때까지 기다릴 때는 어떻게 처리하시나요? isLoading을 여러 개 선언하시나요? 넵 배민에서도 주문 등 여러 프로덕트 부분에서 따로 로딩을 처리하고 있습니다.
  • client store는 어떤 거를 쓰시나요? Mobx 간단하고 파워풀해서
  • query key들이 중복되면 안되는데 어떻게 관리하시는지? 함수명을 쓰고 있다.
  • 도메인별로 쿼리를 관리한다고 하셨는데, 화면에서도 공통적으로도 사용하는 쿼리들이 있을 것 같아요. 이러한 쿼리들은 도메인을 어떻게 분리해서 관리하시나요? 파트별로 도메인을 분리하는데 공통적인 것들은 따로 따서 여러 도메인에서 재사용한다. Mutation도 똑같은 파일에 묶어서 관리한다.
  • redux, react-query를 사용할 때 구조 설계는 어떻게? 클라이언트, 서버 따로 사용한다고 보면 된다.
  • 리액트 쿼리 도입 후 스토어에 남은 순수 클라이언트 상태는 어떤 것이 있나요? UI 관련, 팝업이 열리거나 닫히거나, 회원정보 등 일반적으로 생각하는 공통 상태
  • 쿼리 무효화 시 invalidate를 사용해라.
profile
Detail makes difference.

0개의 댓글