코드노트

React-Query와 Zustand의 차이, 사용하는 목적 정리 본문

Code note/codenote

React-Query와 Zustand의 차이, 사용하는 목적 정리

코드노트 2023. 5. 10. 04:06

 

 

리액트를 하면서 항상 고민하게되는 부분은 상태관리인거 같다.

이번 팀 프로젝트를 진행하면서 리덕스를 사용하지않고 ReactQuery와 Zustand를 통해 상태관리를 하기로 하였다.

두 상태관리 라이브러리의 차이와 목적에  대해서 정리해보려고 한다.

 

 

프론트엔드에서 상태관리에 있어서 가장 처음 만나게된건 리덕스였다.

리덕스를 하게되면서 전역관리를 알게되고 redux-toolkit, redux-saga,  redux-thunk 등등 미들웨어도 사용했다.

 

 

리액트 상태관리는 리덕스?

- 컴포넌트끼리 상태값을 직접 소통하게된다면 컴포넌트 재사용에 있어서도 좋지 않으며,

불필요한 리렌더링이 발생하거나 코드들도 복잡해지게된다.

 

- 리덕스로 상태관리를 할때 장점도 있었지만 단점들이 있었다. 먼저 리덕스에 대해서 정리해보자.

 

전역으로 상태 관리

- 스토어를 통해서 상태를 관리한다.

- 컴포넌트들끼리 상태값을 공유하는게 아니라 스토어와 바로 직접적으로 공유하기때문에 접근성이 편해진다. 전역관리에 있어 효과적이다.

- 단방향 모델링 : Flux패턴으로 단방향의 데이터 흐름을 가지고 있기 때문에 action객체만 따라가더라도 정확히 어떤 데이터가 변화가                                 일어나고 있는지 추적하기가 쉽다.

리덕스의 단점

- 리덕스를 사용하게되면 작은규모의 기능이더라도 필수로 작성해야하는 코드가 많아진다.( 폴더구조,  파일들의 량도.. )

- 중복 코드가 발생한다. 리덕스를 사용하면 같은 기능을 하는 액션 생성자, 리듀서, 미들웨어 등 중복해서 나타날 수 있다. 가독성 또한 해친다.

- 동기화 문제 발생, 리덕스는 상태를 한곳에서 관리하기 때문에 여러 컴포넌트에서 동일한 상태를 사용하는 경우를 생각해보자.

   한 컴포넌트에서 상태를 업데이트하게 된다면 이전 상태를 참조하고 있는 컴포넌트는 예상치 못한 동작을 유발할 수 있다.

 


그럼 리액트쿼리, Zustand는 어떻게 다르며, 리덕스와는 어떻게 다를까?

리덕스를 사용하지않고 두 라이브러리를 사용한 이유를 알기 전에 두개의 차이를 먼저 정리해보자!


ReactQuery

- 서버데이터를 관리하는데에 사용되는 라이브러리다.

- 서버 API 호출을 추상화하고, API 호출 결과를 캐시, 무효화 및 재요청 등의 기능을 제공하여 동기화 문제를 해결할 수 있다.

 

Zustand

- 클라이언트 데이터를 관리하는데 사용되는 라이브러리다.

- 전역적인 상태를 관리할 때 복잡한 상태 관리를 간단하게 처리할 수 있다.

 

* 서버 데이터와 클라이언트 데이터의 차이

- 서버 데이터는 웹서버에서 생성하거나 저장하는 데이터로 클라이언트에서 요청을 보내고, 서버에서 처리된 결과를 받아오는 데이터

- 클라이언트 데이터는 사용자가 클라이언트에서 생성하거나 저장하는 데이터로 로컬스토리지 또는 쿠키와 같은 브라우저 기능을 이용하여 관리된다. 클라이언트 데이터는 일반적으로 개인적인 정보나 환경설정 등을 저장하는 용도로 사용된다.

 


ReactQuery

- 리액트쿼리는 API호출로 인한 데이터를 보낼때 데이터패칭, 캐싱, 동기화 등을 보다 쉽게 만들어준다.

 

 

리액트 자체만으로는 가능하지않을까...? 응..가능은하다...

 

데이터 패칭: 커스텀 훅을 통해서 가능하다. useEffect, useCallback을 통해서 가능

const [data, setData] = useState(null);
const fetch = useCallback(() => {...}, []);
useEffect(() => { fetch() }, []);
return {data, refetch: fetch}

 

캐싱: 리액트 ContextAPI조합을 통해서 가능

const CacheContext = React.createContext(null);
cosnt {Provider} = CacheConntext;
function CacheProvider({childrec}) {
	const [chche, setCache] = useState({});
    ...
    return <Provider value={value)>{children}</Provider>;
}

 

동기화: 리액트쿼리처럼 여러가지 트릭을 통해 사용자의 행동에따라 새롭게 가져올 수 있다.( 예시 코드 focus )

useEffect(() => {
	const interval = setInterval(handleFocus, cacheTime)
    window.addEventListener("focus", handleFocus, false);
    ...
    return () => {
    	clearInterval(interval);
        window.removeEventListener("focus", handleFocus, false);
        ...
    }
}, [])

이 외에다 다양한 기능들이 있지만 이러한 기능들을 하나하나 구현한다고 생각해보자.

테스트부터 시작하여 무엇보다 시간이 오래걸릴것이다.

 

팀프로젝트를 하는 시간은 길지 않았고, 그 시간동안 팀 내에서 이러한 훅, 테스트코드들을 작성하며 서비스를 완성하기에는 부족했다.

 

손에있는 차키를 두고 자전거를 타는..?

걸어가는 느낌이라고 해야할까?

기어가는느낌이지 않을까..?

 

이 외에도 리액트에서는 ContextAPI, 캐싱, 에러핸들링..useState, useEffect... hooks...들에 관한 여러가지 고려사항들도 생각해야한다. 로직이 길어질것이니깐...


그럼 리액트 쿼리로는?

QueryClient, useQuery, useMutation

으로 해결이 가능하다.

 

앞서 코드를 본것처럼 리액트 쿼리 없이 불가능하지 않다.

 

- React에서 제공하는 기본 Hook만으로도 가능하며, 비동기 처리 커스텀 훅도 만들 수 있을 것이다.

- 필요한 기능만 구현할 수 있기도하며, 라이브러리도 설치할 필요가 없다.

 

그러나 기본 Hook을 만들기 위해서는 시간과 노력이 필요하다.

그리고 리액트쿼리에 대비하여 안정성을 확보하기가 어렵다.

 

 

그럼 리액트쿼리에 대해서 안정성을 확보할수있냐는 질문에는?

- 다운로드 수, 주기적인 업데이트, github star 34.4k...믿음직스럽다..


그래서 서버데이터를 리덕스가 아닌 리액트쿼리를 통해 관리하는 이유는? 이렇게 정리할 수 있을것 같다.

 

- 리덕스는 미들웨어를 통해서 비동기 데이터를 상태에 저장하여 사용할 수 있지만,

● 리액트쿼리자체는 서버데이터를 위한 상태관리 라이브러리다.

 

- 리덕스는 API상태에 따라서 로딩, 에러, 성공 처리 또한 별도로 생성하여 사용해야한다.

(RTK Query를 통해서 비교적 쉽게 구현이 가능하지만 store를 통해야하며, 미들웨어를 사용하는것은 동일하며, createApi함수를 통해 만들어 데이터를 조작하고 변형하는데에도 많은 옵션값들이 필요한걸 알 수 있다.) 큰 보일러플레이트가 필요..

 리액트쿼리는 기본적으로 로딩, 에러 상태에 대한 값이 제공이된다.  에러핸들링에도 용이하다.

 

- 리덕스는 다른 API가 추가될때마다 액션, 리듀서, 미들웨어의 코드가 함께 작성되어야한다.

리액트쿼리는 각 Query를 통해서 사용되는 컴포넌트, 커스텀 훅에서 생성되고 사용된다.

그외에도 Query키 또는 enabled값을 통해서 직관적으로 관리할 수 있다.


가장 큰 이유 중 하나는

현재 프로젝트에서 사용되는 대부분의 데이터들은 서버데이터이다.

- 클라이언트데이터는 사용되는 비중이 1/10정도로 볼 수 있다. 아니면 그보다 작을수도..?

 

그럼 서버데이터와 클라이언트데이터를 Redux를 통해서 관리하는게 맞을까? 라는 생각도 하게 되었다.

두 데이터를 다르게 관리하는게 더 좋지 않을까? 라는 의문도 들었다.

 

리덕스 한개로 서버데이터와 클라이언트데이터를 관리하는것보다

서버데이터는 리액트쿼리

클라이언트데이터는 Zustand로 관리하는게 용이하겠다라는 결론이 내려졌다.


다른 비동기 상태 라이브러리는?

RTK-Query - 앞서 말했듯이 RTK Query는 리덕스의 구조를 그대로 가져간다.

Apollo - 스키마를 정의해야한다. 사용법에 대해서 아직은 무지한것도 있다.

SWR - 리액트쿼리와 유사하지만 enabled옵션을 제공하지 않는다. 리액트쿼리가 더 친근하다.

 


클라이언트 데이터를 Zustand로 사용하는 이유와 Zustand의 가벼운 정리

 

Zustand

왜 zustand인가?

현재 상태관리 라이브러리중에서는 jotai, recoil, zustand, mobx를 볼 수 있다.

- 다운로드 수 또한 zustand가 급 성장되고 있는 모습을 볼 수 있다.

- 번들 사이즈 또한  redux를 포함해서도 가장 낮다. recoil에 비하면 20배 이상 차이가 난다..1살도 안된 recoil은 아직..음..

 

- 그리고 정말 쉽다. 정말정말정말 쉽다...

 

- 리덕스를 사용해봤다면 정말 어렵지 않게 사용할 수 있다.

- 리덕스에서 만들었던 스토어와는 다르게 create함수를 통해서 스토어를 생성하고 useState처럼 초기상태값을 설정하고 액션을 설정하면, set함수를 통해 스토어에 상태를 업데이트 시킬 수 있다.

 

 

Zustand를 사용한 이유

- 이번 프로젝트도 마찬가지이지만 프로젝트를 진행할때 시간이 많은경우는 없었던것 같다.

리덕스의 기본지식이 있는 팀원들이였고,

사용법에 대해서 어렵지않게 다들 따라올 수 있었다.

 

- 클라이언트 데이터를 많이 사용하지 않기 때문에 리덕스를 통해 불필요한 코드작성들과 보일러플레이트를 막고자 하였고,

쉬운 사용방법으로 props drilling을 막기위해 사용하였다.


각 프로젝트마다 사용해야할 상태에 있어서 맞는 상태관리 라이브러리를 사용하는게 좋은것 같다. 그리고 중요한것 같다.

거기에는 프로젝트의 규모도 있을것이고, 복잡도에 따라서 달라질 것이다.

팀프로젝트의 경우에는 개발자의 경험도 중요하다고 생각이 든다.

앞으로에 있는 팀프로젝트에서도 각 팀원들에게 맞는 라이브러리를 선택하고,

프로젝트에 맞는 라이브러리와 그에 뒷받침이 될 수 있는 이유들을 정리해보도록 해야할것 같다.

 

 

 

참조

- 카카오 유튜브 [Frontend] 눈에 보이지 않는 개선: My구독의 Redux에서 React-Query 전환 경험 공유 / if(kakao)dev2022

- 오픈소스컨설팅, 강동희,  React-Query 도입을 위한 고민 (feat. Recoil)