Context API 없는 상태관리 Zustand

hodu·2023년 11월 13일
1

Library

목록 보기
1/5
post-thumbnail

✅ 개요

최근 Zustand에 관해 공부하고 사용해보는 경험을 갖었다.
예전부터 Zustand는 꼭 사용해보고 싶은 라이브러리였다.
npm trend를 보면 아래와 같은 통계를 볼 수 있다.

상태관리 라이브러리 npm 다운로드 횟수는

Redux : 675만
Zustand : 220만
jotai : 63만
recoil : 49만

인것을 볼 수 있다.
작년과 비교해보면

Redux : 675만 (43만 상승)
Zustand : 220만 (135만 상승)
jotai : 63만 (43만 상승)
recoil : 49만 (9만 상승)

jotai와 Redux는 43만 상승하였지만, Zustand는 약 3배인 135만 증가하였다.
(전체적으로 코딩 인구가 급증했다는 것도 보여주기도 한다.)

호기심이 안생길 수 없었다.


👍 Redux 보다 좋은 점

github Readme를 보면 Redux의 어떠한 단점을 커버하려고 했는지 잘적혀있다.

Why zustand over redux?
1. Simple and un-opinionated
2. Makes hooks the primary means of consuming state
3. Doesn't wrap your app in context providers
4. Can inform components transiently (without causing render)

  1. 심플하고 단순하다.
  2. HOC -> HOOK으로
  3. 컨텍스 제공자를 쓰지 않는다.
  4. 렌더링 없이 상태 변화가 가능하다.

1번과 2번은 Recoil에서도 가능하다.
주목해야할 점은 3번과 4번이다.

코드 설명은 위 장점을 중심으로 설명할 예정이다.


Why zustand over context?
Less boilerplate
Renders components only on changes
Centralized, action-based state management

다음으로는

왜 context를 사용하지 않는가?에 관한 이야기다.
context API는 React 상태관리에서 당연시 여겨졌지만, Zustand는 사용하지 않고 그러한 이유를 적었다.


보일러 플레이트를 줄일 수 있다. 👍

Context API를 사용하면 종종 Context.Provider와 Context.Consumer를 사용해야 하고, 별도의 로직을 작성해합니다. 이런 보일러플레이트(Boilerplate) 코드를 줄이기 위해 context API를 사용하지 않습니다.


변경 시에만 구성요소를 렌더링합니다. 👍

Context API는 상태 변경이 있을 때, 사용하는 모든 컴포넌트를 리렌더링합니다. Zustand는 Context API 사용하지 않아서 상태 변경에 따른 렌더링을 더 효율적으로 관리할 수 있습니다.


중앙 관리하는 액션 기반 상태관리 👍

모든 상태(데이터)를 한 군데에서 관리하고, 특정 '액션'을 실행해서 상태를 변경하는 방식으로 돌아간다.
Context API보다 더 단순하게 상태를 공유하고 바꿀 수 있어서, 코딩이 간단하다.


Zustand는 Redux나 Context API의 단점을 보완하면서, 더 단순하고 효율적인 상태 관리를 제공하려는 목적으로 설계하였다.


✅ 간단한 사용법

설치는 간단하게 npm이나 yarn을 이용한다.

npm install zustand
yarn add zustand
pnpm add zustand

이제 코드를 보면

장점 1. 심플하고 단순하다.

위의 장점을 볼 수 있다.

🕵️‍♀️ 스토어 생성

import { create } from 'zustand'

const useBearStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

create로 스토어를 생성하고, bears라는 상태를 추가하고,
increasePopulation, removeAllBears이라는 함수를 만들어서 관리할 수 있다.
store 안에서는 set을 사용하여 값을 업데이트 한다.

🤜 Zustand store 사용하기

장점 2. HOC -> HOOK으로

아까 만든 스토어를 import 하고, 상태를 선택하면 변경 사항에 따라 구성 요소가 다시 렌더링한다.

function BearCounter() {
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here ...</h1>
}

function Controls() {
  const increasePopulation = useBearStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

bears와 increasePopulation는 아까 store 에서 만들었던 상태와 함수이다.

increasePopulation: () => set((state) => ({ bears: state.bears + 1 }))

increasePopulation를 사용하여서 값을 1 크게 만들면, bears는 업데이트한다.

이게 끝이다.

장점 3. 컨텍스 제공자를 쓰지 않는다.

적은 보일러플레이트와 context API를 사용하지 않고 Zustand를 사용할 수 있다.


🙌 subscribe

자주 발생하는 상태 변경의 경우, 구독 기능을 사용하자.

장점 4. 렌더링 없이 상태 변화가 가능하다.

마지막 장점인 렌더링 없이 상태 변화가 가능하다.

코드는 마운트 해제 시를 고려해서 useEffect와 결합하는 것이 가장 좋다. 이는 뷰를 직접 변경할 수 있는 경우 성능에 큰 영향을 미칠 수 있습니다.

const useScratchStore = create((set) => ({ scratches: 0, ... }))

const Component = () => {
  // Fetch initial state
  const scratchRef = useRef(useScratchStore.getState().scratches)

  useEffect(() => useScratchStore.subscribe(
    state => (scratchRef.current = state.scratches)
  ), [])

Ref를 통해 값을 구독하고, 해당 값에 변화가 생기면 렌더링 없이 상태 변화가 가능하다.

직접 결과를 확인할 수 있게 codesandbox를 남기겠다.
카운터 버튼을 통해 렌더링 없이 값을 업데이트하는 것을 볼 수 있다.

  useEffect(() => useScratchStore.subscribe(
    state => (scratchRef.current = state.scratches)
  ), [])

위 로직을 부면 구독을 선언하는 동시에 return을 주고 있는데 이를 통해 언마운트 시에는 구독 해제를 하도록 세팅하고 있다.


끝으로 😊

zustand를 사용하면서 느꼈던 점은 store인 동시에 hook이라는 점이 편리하였다.
store 관련 함수를 한 곳에 모와서 관리할 수 있고, store에서 관리하니 동기화 걱정도 없었다.

라이브러리를 사용할 때, 해당 Readme나 guide를 보면 최고인 것 같지만, 항상 단점도 존재한다 그래서 단점을 찾아보고 있는데 아직 찾지를 못했다.
아무래도, context API 없는 것이 다른 상태관리에 비해 많이 유리하지 않을까 한다.

zustand를 사용하여서 성능을 개선해보고 싶다.

profile
잘부탁드립니다.

3개의 댓글

comment-user-thumbnail
2024년 11월 20일

zustand가 전역 관리라고만 한정 짓는데 아닙니다. zustand는 실 데이터를 사용하는 컴포넌트만 렌더링하므로
context API의 지역적 관리의 이점도 같이 가져갑니다.
API가 풍부한 zustand를 냅두고 굳이 Context API로 돌아갈 필요는 없어보이네요.

1개의 답글