이 글은 우아한 테크세미나 배민근님의 react query와 상태관리 발표자료를 보고 정리한것입니다.
why?
React query는 왜사용해야할까?
현재 NextJS로 규모가 아주큰 쇼핑몰과 같은 프로젝트를 진행하고있습니다.
항상 반복을 줄여야한다는 생각으로 axios통신은 AxiosManager.ts 를 만들어 header나 Authorization, baseURL 등을 관리할 수 있도록 했고, 컴포넌트는 틀은 같은데 값만달라지는 경우가많아, props를 받아 데이터를 display하도록만 하도록 작성했습니다.
하지만 가장 중요한게 남아있죠. 바로 FE의 최대숙제인 "상태관리"입니다.
전역적으로 관리되야하는 상태들은 모두 Recoil로 관리를 하였습니다.
이미 개발이 80프로는 완료된 상황에서 느낀거지만 웹에서 보여주는 데이터들은 모두 DB에서 CRUD가 이루어지고 그 값을 가져오는 것이 기본이었습니다.
POST, PUT, DELETE 작업이 들어가면 필연적으로 데이터를 fetch 해주어야했기때문에, 이부분도 개선할 수 있겠다는 생각이 들었고, 얼핏 들었던 react query의 용도를 알아보니, 저에게 필요한 기능들을 제공해주고있었고 공식문서를 찾아보고 사용법은 익힌 후 좀 더 깊은 생각을 듣고싶어 유튜브를 찾던 중 우아한 테크세미나에서 이 주제를 다루었길레 바쁜지만 지금이 세미나를 듣는 2시간이 필연적이라 느껴 보고 내용을 정리해 봅니다.
상태관리라하면 떠오르는 것
Redux, MobX, Recoil ...
왜 이런 라이브러리들을 사용했을까요?
리액트의 데이터 흐름은 단방향이기 때문에 컴포넌트의 깊이가 깊어질 수록 전달해야하는 state 가 많아지기 때문에 props drilling이 발생합니다. 이 문제를 해결하기위한 라이브러리 들이죠.
상태란?
상태는 주어진 시간에 대해 시스템을 나타낸다고 표현합니다. 쉽게말하면 언제든지 변경될 수 있는 데이터를 말하죠.
그런데이터의 종류에는 문자열,숫자,배열,객체 등이 있고, 이것들이 응용프로그램에 저장되어있다면 그것을 상태라고 할수있습니다.
개발자의 입장에서는 관리해야하는 데이터를 말하죠
현대에는 UI/UX가 중요해지면서 프로덕트가 커지고, FE에서 역할이 늘어나고 그만큼 관리해아할 상태가 늘어나게 됬습니다.
Store vs API
프론트에서 store란 전역상태가 저장되어있는 장소입니다.
하지만 저장된데이터들을 다시한번 생각해본다면, 소유주체가 사용자가 아닌경우가 많습니다.
결국 데이터는 서버의 DB에 있고, 그것을 가져와서 보여주는 것일 뿐이니까요.
매번 API를 호출했을때 성공과 에러의 경우를 관리해야하고, 비슷한 구조의 API코드를 작성하게 됩니다.
서버에서 받아야하는 상태들의 특성
- 소유주가 client가 아니라 원격 공간에서 관리되고 유지된다.
- fetching, updating 에 비동기 API가 필요하다.
- 제때 fetch가 되지 않으면 변경된 데이터일 수 있다.
Client State vs Server State
ownership이 누구에게 있느냐에 따라서 상태를 나누어 볼 수 있습니다.
Client State의 특징
- client에서 온전히 소유하고 제어가 가능하다
- 초기값 설정이나 조작에 제약사항이 없다.
- 다른사람들과 공유되지 않으며 클라이언트 내에서 사용자의 인터렉션에 따라 변할 수 있다.
- 항상 client 내에서 최신상태로 관리된다.
Server State의 특징
- client가 소유하는 것이 아닌, 원격의 공간에서 관리되고 유지된다.
- fetch, update에 비동기 API 가 필요하다.
- 다른사람들과 공유되는 데이터이고, 사용자가 모르는 사이에 변경될 수 있다.
- 신경쓰지 않는다면 잠재적으로 out of date가 될 가능성을 지닌다.
React Query 소개
Fetch, Cache, Update data in React and React Native all without touching any 'global state'
공식문서에서 말하는 react query는 전역state없이 리액트에있는 데이터를 패치,업데이트, 캐싱해주는 도구라고 소개합니다.
또 강조하는 부분은 선언적, 자동, 심플, 리액트 친화적, 파워풀, zero-config로 시작하능하고 필요에따라 customizing이 가능하다는 점입니다.
3가지 core 컨셉
Queries
const { data, isLoading } = useQuery(queryKey, queryFunction, options)
data Fetching용이자 CRUD 에서 Reading의 역할만 하는 녀석입니다.
React Query는 queryKey에 따라 query caching을 관리합니다.
String | Array 형태가 들어갈 수 있습니다.(실무에서는 Array를 많이 사용한다고 합니다)
- queryFuntion은 promise를 반환하는 함수를 작성해야합니다. 즉 fetch나 axios 가들어가게 되겠죠
- options에는 어떤 것들이있을까요?
onSuccess, onError, onSettled : query fetching 성공/실패/완료 시 실행할 side Effect를 정의할 수 있습니다.
enabled : 기본적으로 useQuery는 마운트되는 시점에 바로 실행되는데, 이옵션을 false로 해주면 원하는 시점에 실행할 수 있습니다.
retry : query 동작 실패시 자동으로 다시 시도
select : 성공시 가져온 data 가공
keepPreviousData : 새롭게 fetching시 이전 데이터 유지 여부
refetchInterval : 주기적으로 refetch를 할지 결정
.. 등등
useQuery가 반환하는 값들은 굉장히 많습니다. 너무많아서 공식문서를 참조하시면 될거같구요
주요로 사용되는 것들은 아래와 같습니다. 직관적으로 이해하기 쉬울거에요!
const {data,error,isFetching,status,isLoading,isSuccess,isLoading,refetch,remove...} = useQuery();
위와같이 유용한 여러 인터페이스를 제공해줍니다.
Mutations
데이터 생성/수정/삭제 용
const {mutate} = useMutation(newTodo => axios.post('/todos', newTodo),option)
query와 다르게 즉시실행되지 않고, mutate() | mutateAsync() 를 호출시 실행됩니다.
reset: mutation내부 상태 clean
나머지는 특별할 것없이 useQuery랑 비슷합니다. 오히려 반환하는 객체안의 내용이 더적어서 쉽습니다.
options
onMutate : 본격적인 Mutation 동작전에 먼저 동작하는 함수, Optimistic update 적용할 때 유용합니다.
Optimistic update : 페이스북의 좋아요버튼을 동작을 생각해볼때, 누르면 바로 색이 칠해집니다. API통신이 그렇게 빨리되지 않았음에도 불구하구요. 바로 이때 사용되는 것입니다. 낙관적으로 결과를 예측하고 미리 동작시킨후 만약 Error가 발생하면 rollback시킬 수 있습니다.
Query Invalidation
queryClient.invalidateQueries(); // 모든 쿼리 캐싱 삭제
queryClient.invalidateQueries('todos'); //todos 쿼리 캐싱 삭제
캐싱을 삭제하게된다면, 바로 다시 fetch를 동작시켜 신선한 데이터를 받아옵니다.
즉, refetch를 하는것이죠.
이때 인자로 넣어준 'todos' key를 가진 query는 stale 취급이되고, 현재 rendering되고있는 query들은 백그라운드에서 refetch 됩니다.
stale: 신선하지 않은
Caching & Synchronization
options
cacheTime : 메모리에 얼마만큼 있을건지 설정 (default : 5분)
staleTime : 얼마 시간이 흐른 후에 데이터를 stale 취급할 것인지 (default : 0, 바로 stale취급이 되버리는 것임)
refetchOnMount / refetchOnWindowFocus / refetchOnReconnect -> 가 true 이면 Mount / window focus / reconnect 시점에 data가 stale이라고 판단되면 모두 refetch를 수행합니다. 이 세개의 값들은(default : true)
default config
staleTime : 0
cacheTime: 5분
refetchOnMount / refetchOnWindowFocus / refetchOnReconnect : true
query실패시 3번 retry
전역상태처럼 관리되는 데이터들
staleTime이 30초이고, 'todo'라는 key를 가진 query를 A컴포넌트, B컴포넌트에서 호출되었다고 칩시다.
A컴포넌트가 마운트된 후 10초뒤에 B컴포넌트가 마운트되면 fetch를 수행하지 않습니다.
state가 전역으로 관리되고있지않고, 각각의 컴포넌트인데 어떻게 그렇게 수행될까요?
이게 가능한 이유는 react query 내부적으로 Context API를 사용하고있기 때문입니다.
마무리
배민근님께서도 말씀하셨지만, 어떤 기술을 사용할 때는 why가 중요하다고 생각합니다.
저는 진행하고있는 프로젝트의 규모가 크다보니 다뤄아할 데이터도 많고, 전역store가 비대해지는 이슈가 생겼습니다.
단순히 서버에있는 데이터만 가져와서 쓰는 것일 뿐임에 말이죠..
그리고 모든데이터가 서버에서 관리되고 유지되다보니, 어떤 CRUD동작을 했을 때 반드시 새로운 데이터를 fetch해와야했습니다. 그런면에서 query는 key값을 가지고 refetch기능과, caching 까지 제공해주어서 필요성을 느끼게 되었습니다.
이미 개발이 거의완료된 상태이고, 지금와서야 도입한다는 것은 코드가 일관성이 없어지겠지만, 어차피 깔끔하게 하려고해도 어느순간 그런 규칙들이 무너져있는 제코드를 많이봐왔고, 지금 프로젝트도 그런것 같기때문에.. 그런 두려움은 없습니다.
이것저것 시도해보고, 괜찮다싶으면 차후에 전부 리팩토링을 진행해도 되고..!
이렇게 글을 마무리 하도록 하겠습니다.
'Archive' 카테고리의 다른 글
AWS serverless (API gateway + lambda) (0) | 2022.06.04 |
---|---|
AWS EB 사용하기 (0) | 2022.06.03 |
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! 문제 (0) | 2022.05.07 |
It is likely you do not have the permissions to access this file as the current user 에러 (0) | 2022.05.07 |
아무설정필요없이 Docker로 mariadb 바로설치하고 사용하기 (0) | 2022.05.05 |