어떤 상황에서 Zustand와 Jotai를 선택해야 할까?

ClydeHan·2024년 10월 16일
37

Zustand vs Jotai

Zustand vs Jotai image

주스탠드(Zustand)는 Redux와 유사하고, 조타이(Jotai)는 Recoil과 유사한 방식으로 상태 관리를 제공한다. Redux는 불필요한 복잡성, 장황한 코드, 많은 보일러플레이트로 인해 관리가 어려운 문제가 있으며, Recoil은 페이스북에서 개발했으나 현재 업데이트 속도가 느려지고 있어 기술의 장기적 지속 가능성에 우려가 있다.

흥미로운 점은 주스탠드와 조타이가 같은 개발자(Daishi Kato)에 의해 개발됐다는 것이다. 두 도구는 더 간단하면서도 강력한 상태 관리 솔루션으로 주목받고 있으며, 각각 다른 철학과 구조를 기반으로 설계되어 필요에 따라 적절히 선택하는 것이 중요하다. 전역 상태 관리와 리렌더링 성능 최적화 측면에서도 큰 차이가 없으므로, 프로젝트 요구사항에 따라 선택하면 된다.

기본적으로 Zustand와 Jotai 모두 상태가 변경될 때 해당 상태에 구독되어 있는 컴포넌트들만 리렌더링되도록 최적화되어 있다. 이는 두 라이브러리의 주요 장점 중 하나로, 불필요한 리렌더링을 줄여 성능을 향상시키는 방식이다.

또한, 주스탠드와 조타이는 지속적인 업데이트와 개발자와의 소통을 통해 기술의 장기적 지속 가능성과 최신 트렌드에 부합하는 점, 그리고 학습 용이성 측면에서도 매력적인 선택지로 평가받고 있다.

이 글에서는 두 도구의 상태 관리 방식, 사용 패턴 등 주요 차이점을 깊이 있게 다루며, 어떤 상황에서 각각의 도구를 사용하는 것이 적합한지 알아볼 것이다.

Jotai는 일본어로 "state(상태)"를 의미한다.

Zustand는 독일어로 "state(상태)"를 의미한다.

📌 차이점 미리보기

기능/특징ZustandJotai
상태 관리 방식중앙 집중형, 단일 스토어분산형, 여러 개의 작은 아톰(원자) 단위
리렌더링 제어전역적으로 최적화 가능개별 아톰 단위로 세밀한 제어 가능
사용 패턴Flux 패턴, 중앙 스토어에서 모든 상태 관리Atomic 패턴, 각 컴포넌트에서 개별적으로 관리
상태의 범위React 외부에서도 접근 가능React 컴포넌트 외부에서 직접 접근 불가

📌 상태 관리 방식

💡 Zustand (중앙 집중형 방식, top-down)

Zustand는 하나의 중앙 스토어(store)를 생성해, 모든 상태를 그 안에 정의하고 관리한다. 컴포넌트들이 그 스토어에서 필요한 상태를 불러와 사용하며, 상태가 중앙에서 관리되기 때문에 탑다운 방식이라고 한다. 즉, 중앙 집중적으로 상태를 관리하고, 컴포넌트들이 그 상태를 필요할 때마다 불러와 사용한다.

상태를 단일 Store 객체로 정의하고, 상태와 업데이트 로직을 Store에서 한꺼번에 관리한다. 각 컴포넌트는 Store에서 상태를 가져와 사용하는 방식이며, 상태 접근 및 업데이트 로직이 한 곳에서 관리되는 일관된 패턴을 따른다.

예를 들어, 블로그 웹사이트를 만든다고 하면, 하나의 중앙 스토어에서 블로그 목록, 사용자 정보, 포스트 데이터 같은 것을 관리하고, 필요한 컴포넌트들이 이 중앙 상태를 공유하게 된다.

모든 상태가 한 곳에서 관리되기 때문에, 애플리케이션의 전체 상태를 쉽게 파악할 수 있다. 상태를 추적하거나 디버깅할 때 중앙 저장소만 보면 되므로 유지보수가 용이하다.

하지만 반대로, 상태가 복잡해질수록 관리하는 스토어가 거대해지고, 의존성이 커질 수 있다. 상태가 거대해질수록 특정 상태가 업데이트될 때, 다른 많은 컴포넌트가 그 상태를 필요로 할 경우 의존 관계가 복잡해지고, 변경 사항을 처리하는 데 여러 고려 사항이 생기게 된다. 특히 서로 다른 상태들이 강하게 연결되어 있을 때, 이를 분리하거나 특정 컴포넌트에서만 사용하는 상태로 유지하기가 어려워진다.

예를 들어, 중앙 스토어에 상태들이 많이 포함되어 있다면, 특정 상태가 업데이트될 때 의도치 않게 다른 상태나 의존하는 컴포넌트들이 영향을 받을 수 있다. 이를 관리하고 추적하는 것은 프로젝트 규모가 커질수록 점점 더 어렵고 복잡해질 수 있다.

쉽게 설명하자면, Zustand는 애플리케이션의 상태를 한 곳에서 모아 관리하는 방식으로, 리액트의 기본 상태 흐름과 비슷하게 동작한다.

이 방식은 전체 상태를 중앙에서 일괄적으로 관리하고 추적하기 때문에, 애플리케이션의 상태를 쉽게 파악할 수 있고, 변경도 한 곳에서 관리할 수 있어 간편하다. 예를 들어, 쇼핑몰 애플리케이션에서 '사용자 정보'나 '장바구니'와 같은 데이터가 있다고 할 때, 이 모든 데이터를 하나의 큰 저장소에서 관리하고, 필요한 컴포넌트들이 그 데이터를 공유하며 사용하게 된다.

이런 방식은 여러 컴포넌트에서 동일한 데이터를 공유해야 하거나, 애플리케이션 전체에서 사용되는 중요한 상태를 한 곳에서 관리하는 것이 필요할 때 유용하다. 다만, 애플리케이션이 커질수록 중앙에 모인 상태들이 많아져 관리가 복잡해질 수 있다는 단점도 있다.

하지만 Zustand에서는 이런 문제를 개선하기 위한 방법을 제시한다.

Slices Pattern - Zustand

// 개별 스토어 생성
export const createFishSlice = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

// 개별 스토어 생성
export const createBearSlice = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})

// 두 스토어 결합
import { create } from 'zustand'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'

export const useBoundStore = create((...a) => ({
  ...createBearSlice(...a),
  ...createFishSlice(...a),
}))

// 결합된 스토어 사용
import { useBoundStore } from './stores/useBoundStore'

function App() {
  const bears = useBoundStore((state) => state.bears)
  const fishes = useBoundStore((state) => state.fishes)
  const addBear = useBoundStore((state) => state.addBear)
  return (
    <div>
      <h2>Number of bears: {bears}</h2>
      <h2>Number of fishes: {fishes}</h2>
      <button onClick={() => addBear()}>Add a bear</button>
    </div>
  )
}

export default App

💡 Jotai (분산형 방식, bottom-up)

Jotai는 상태를 작은 원자(atom) 단위로 나누어 관리하는 분산형(bottom-up) 접근 방식을 따른다. 각 컴포넌트는 자신이 필요한 atom을 구독하여 그 상태를 사용하며, 상태는 중앙에서 관리되는 것이 아니라 각 컴포넌트에서 독립적으로 관리된다. 이러한 접근 방식을 바텀업 방식이라고 부른다. 즉, 상태가 분산되어 컴포넌트에서 필요할 때마다 불러와 사용하는 구조이다.

Jotai의 철학은 각 상태를 작은 Atom 단위로 나누어 관리하며, 각 Atom은 개별 컴포넌트에서만 구독되고 사용된다. 상태가 서로 분리되어 있기 때문에, 컴포넌트마다 필요한 상태만 가져오며, 필요에 따라 Atom들 간에 의존 관계를 형성해 파생된 상태를 만들 수도 있다.

예를 들어, 블로그 웹사이트에서 포스트의 제목, 내용, 작성자 정보 등을 각각 개별적인 atom으로 만들어 각 컴포넌트가 관리하고 업데이트하는 방식이다.

이러한 접근 방식은 React의 useState와 유사한 개념으로, 각 상태를 개별적으로 정의하고 사용하는 방식이다. 이를 통해 보다 세밀한 리렌더링 제어가 가능하다. 특정 상태가 변경될 때, 그 상태에 의존하는 컴포넌트들만 영향을 받게 되므로 복잡한 애플리케이션에서 성능 최적화에 유리하다. 상태가 독립적으로 관리되기 때문에 변경 사항이 다른 상태로 전파되지 않는다는 장점도 있다.

세밀한 리렌더링 제어가 가능하다는 것은, 개별 Atom을 구독하기 때문에 상태 간의 영향도가 적다는 것을 의미한다. 상태가 독립적으로 존재하며, 그 상태를 사용하는 컴포넌트만 리렌더링되므로, 상태 간의 강한 연결을 피할 수 있어 복잡한 상태 간의 연쇄적인 리렌더링을 방지할 수 있다.

하지만, Jotai의 접근 방식은 상태를 여러 atom으로 분산하기 때문에, 애플리케이션의 전체 상태를 파악하기 어렵고, 디버깅이 복잡해질 수 있는 단점이 있다. 상태를 여러 컴포넌트에서 공유하려면 추가적인 작업이 필요할 수도 있다. 상태를 잘게 나눈다는 것이 항상 좋은 것은 아니며, 너무 세분화되면 오히려 관리가 어려워질 수 있다.

import { atom } from "jotai";

export const vehicleModelAtom = atom<string>("");
export const vehicleYearAtom = atom<number>(2023);
export const vehicleHorsepowerAtom = atom<number>(150);
export const vehicleFuelTypeAtom = atom<string>("Gasoline");

export const vehicleAtom = atom((get) => ({
  model: get(vehicleModelAtom),
  year: get(vehicleYearAtom),
  engine: {
    horsepower: get(vehicleHorsepowerAtom),
    fuelType: get(vehicleFuelTypeAtom),
  },
}));

이 예시에서 Jotai는 상태를 작은 단위인 atom으로 나누어 정의하며, 필요한 컴포넌트에서만 이 atom들을 구독하여 사용한다. 예를 들어, vehicleModelAtom은 차량의 모델명을 관리하는 상태이고, 다른 Atom들도 각각 독립적인 상태를 관리한다. 이를 필요에 따라 vehicleAtom에서 결합해 사용하면서, 상태 간의 유연한 결합독립적 관리를 동시에 할 수 있다.


📌 컨셉 (상태의 구조적 설계 및 철학적 차이)

💡 Zustand (Flux Pattern)

Flux Pattern Image

주스탠드(Zustand)는 상태 관리에 있어 Flux 패턴을 따르는 라이브러리이다. 이 패턴은 단일 Store 중심으로 상태를 관리하며, 상태가 변경될 때마다 관련된 뷰에 변경 사항을 전달하는 방식이다. 상태 변경은 Action을 통해 트리거되며, 모든 Action은 Dispatcher를 통해 스토어에 전달된다. 뷰에서는 상태를 읽고 변경 사항을 감지하여 UI를 업데이트한다.

Zustand는 중앙 집중형 상태 관리를 지향하여, 모든 상태를 하나의 Store에 모아 관리하고, 여러 컴포넌트가 이를 필요에 따라 구독하는 방식이다. 이는 데이터가 단방향으로 흐르는 Flux 패턴과 유사하며, 상태 관리의 경로가 명확하다. 이 방식은 Redux와 비슷하게 중앙에 스토어를 두고 여러 컴포넌트에서 상태를 공유하는 것을 중점으로 두고 있다.


💡 Jotai (Atomic Pattern)

Atomic Pattern Image

조타이(Jotai)Atomic 패턴을 따르는 상태 관리 라이브러리로, 상태를 작은 원자(atom) 단위로 나누어 관리한다. 각 상태는 독립적으로 정의되며, 다른 상태와의 종속성을 최소화하여 독립적이고 유연한 상태 관리를 지향한다. 이러한 원자적 구조를 통해 상태가 필요한 컴포넌트에서만 해당 상태를 구독하며 업데이트할 수 있어, 상태 간 의존성을 줄이고 리렌더링을 최소화할 수 있다.

조타이는 React의 useContext & useState와 유사한 방식으로 atom을 통해 상태를 정의하고, useAtom 훅을 사용해 상태와 그 변경 함수를 제공한다(훅 기반). 이러한 접근 방식은 직관적이고 간단한 상태 관리를 가능하게 하며, 상태의 독립성과 분산형 관리 철학을 중요하게 여긴다.

  • Jotai 두 가지 원칙
    • Primitive (기본적인): 기본 API는 useState처럼 단순하다.
    • Flexible (유연한): 각 아톰(atom)이 서로 연결되어 복잡한 상태 구조를 만들 수 있다. 아톰은 임의의 다른 아톰에 의해 업데이트될 수도 있다. 이는 복잡한 상태 모델을 추상화할 수 있게 한다.

📌 범위 (상태의 위치)

💡 Zustand (모듈 수준 상태)

Zustand의 상태는 React 외부에 존재한다. React의 useState와 달리 React 외부에서도 접근 가능한 상태다. 이는 상태가 React의 내부 훅 시스템에만 한정되지 않고, 전역적인 JavaScript 메모리에 저장된다는 의미다. Zustand에서는 상태가 중앙에서 관리되기 때문에 어떤 컴포넌트에서든, 심지어 React 컴포넌트가 아닌 곳에서도 이 상태에 접근할 수 있다. 쉽게 말해, 상태가 React와 분리되어 있기 때문에, React 외부의 코드에서도, 애플리케이션 어디서든 상태를 관리하고 사용할 수 있는 것이다.

Zustand는 클로저를 활용해 스토어의 상태를 관리한다. 클로저는 함수가 선언될 때 그 주변 환경을 기억하는 특성으로, Zustand에서 상태는 스토어를 조회하거나 변경하는 함수의 바깥 스코프에 유지된다. 이렇게 함으로써 상태는 애플리케이션의 생명 주기 전반에 걸쳐 일관성 있게 관리되며, React 외부에 위치하면서도 안전하게 상태를 관리하고 사용할 수 있다. 이는 상태가 중앙에서 유지되며, 의도치 않은 변경을 방지할 수 있게 해준다.

상태가 모듈 파일에 정의되고, 어디서나 해당 모듈을 가져다 쓰면 접근할 수 있는 전역 상태이다. 즉, Zustand는 모듈 수준 상태를 가진다.


💡 Jotai (컨텍스트 수준 상태)

Jotai의 상태는 React 내부에 존재한다. 이는 React의 useState와 비슷하게 동작하며, 상태가 특정 컴포넌트 또는 컴포넌트 계층 구조 내에서 관리된다는 의미다. 상태를 관리하기 위해 Jotai의 atomuseAtom 훅을 사용하게 되는데, 이 상태는 React 컴포넌트 시스템 안에서만 접근 가능하다. 즉, React 컴포넌트 밖에서 이 상태에 직접 접근할 수 없다는 의미다. 상태가 각 Provider 내부에 국한되며, Provider는 상태의 범위를 정의하고, 같은 atom이라도 Provider가 다르면 별도의 상태로 간주된다.

<Provider>
  <Counter />  // 첫 번째 Provider의 상태를 공유
  <Counter />  // 첫 번째 Provider의 상태를 공유
</Provider>
<Provider>
  <Counter />  // 두 번째 Provider의 상태를 공유
  <Counter />  // 두 번째 Provider의 상태를 공유
</Provider>

Provider를 여러 번 사용한 이유는, 각 Provider가 별도의 상태를 관리하기 때문이다. 같은 atom을 사용하더라도, Provider마다 서로 다른 상태가 설정된다. 그래서 하나의 Provider에 있는 컴포넌트는 그 Provider의 상태를 공유하지만, 다른 Provider는 별도의 상태를 갖는다. 각각의 Provider가 별도의 상태를 관리하므로, 같은 컴포넌트라도 다른 Provider 내에 있으면 서로 다른 상태를 갖게 된다.

Jotai는 기본적으로 Provider-less 모드를 제공하므로, 별도의 프로바이더 없이 전역 상태로 사용할 수 있다. 단, 필요에 따라 프로바이더를 추가해 특정 상태 범위를 제한하고 상태를 분리하여 관리하기 위해서 범위를 제한할 수 있다. 이는 여러 개의 인스턴스를 가지는 상태를 관리하거나, 특정 컴포넌트 트리에서만 상태를 사용하도록 범위를 설정하고자 할 때 유용하다. 즉, React의 특성에 더 잘 맞는 지원을 한다.

Jotai의 상태는 React 내부에 존재하므로, React 컴포넌트와 깊이 연관되어 있으며 상태의 범위는 특정 Provider 내로 한정된다. Provider는 Jotai에서 사용하는 모든 작은 상태 단위(아톰)들을 보관하고 관리하는 일종의 컨테이너 역할을 하는데, 컴포넌트들이 필요할 때마다 그 값을 가져다 사용할 수 있도록 한다. 이는 동일한 컴포넌트를 여러 번 사용하더라도 각기 다른 상태를 가지도록 할 수 있어 상태 격리가 가능하다는 의미이다.

애플리케이션에서 Provider를 하나만 사용하거나, 별도의 프로바이더 없이 전역 상태로 사용하는 경우 Jotai의 상태 관리 방식과 Zustand의 방식은 거의 비슷하게 작동한다. 두 방식 모두 애플리케이션 전체에서 동일한 상태를 사용할 수 있다는 점에서는 큰 차이가 없다. 하지만, 상태 관리 방식, 구조, 그리고 상태의 범위에 대한 명확한 차이는 다른 목차에서 계속 설명하고 있다.

상태가 React의 Context를 통해 정의되고, 특정 컴포넌트 계층 내에서만 접근 가능한 상태이다. 즉, Jotai는 컨텍스트 수준 상태이다.


📌 번들 사이즈

zustand 번들 사이즈

jotai 번들 사이즈

2024.10.17 기준, Zustand 1.2kb, Jotai 8.3kb로 Zustand가 더 가볍다.


그렇다면 무엇을 선택해야 할까?

실제 Zustand와 Jotai를 조직 단위로 사용해본 경험자들의 리뷰를 정리하자면 아래와 같다.

📌 상태의 구조와 조직화 방식

  • Zustand: 상태를 중앙 집중식으로 관리하며, 스토어에 구조를 강제한다. 이는 상태 관리 방식이 일관되고 전체적으로 비슷하게 보이기 때문에, 대규모 팀에서 유지보수가 용이하다. 특히 Flux 기반 구조를 가지고 있어 데이터의 정규화(평탄화) 작업에 익숙하다면 유리하다. 단, 전역적으로 관리되기 때문에 상태가 너무 커지면 복잡해질 수 있다.
  • Jotai: 각 상태가 독립적인 아톰 단위로 나누어져 있어, 자유롭게 작은 스토어를 사용할 수 있다. 하지만 상태가 세분화되다 보니, 대규모 프로젝트나 팀에서 사용하기에는 상태가 너무 분산되어 혼란을 초래할 수 있다. 아톰이 많아지면 관리가 어려워져서 구조를 정리하지 않으면 혼란이 가중될 수 있다.

📌 팀 규모 및 유지보수성

  • Zustand: 대규모 팀이나 기업 환경에서 사용하기에 적합하다. 여러 개발자가 협업할 때 동일한 구조의 상태 관리 방식이 강제되므로, 상태의 추적이 쉽고 유지보수성이 높다.
  • Jotai: 작은 규모의 프로젝트나 명확하게 정의된 사용 사례에서 적합하다. 상태를 필요에 따라 자유롭게 나눌 수 있어 유연성이 있지만, 대규모 팀에서는 관리가 어려워질 수 있다.

📌 상태의 범위 및 사용 사례

  • Zustand: 전역 상태를 관리하기에 좋다. 예를 들어, 애플리케이션 전체에서 사용하는 테마나 설정, WebSocket 연결과 같은 상태를 관리하는 데 유리하다.
  • Jotai: 컴포넌트 내부에서 상태를 세밀하게 관리하기에 좋다. 많은 상호작용을 하는 컴포넌트들(예: 차트, 테이블, 그래프 등)에서 독립적인 상태를 관리하거나, 빠르게 변하는 데이터와 계산된/파생된 값을 다룰 때 유리하다.

📌 상태 접근성

  • Zustand: 상태가 React 외부에서도 접근 가능하므로, 전역 함수나 React 컴포넌트가 아닌 곳에서도 상태를 참조할 필요가 있는 경우에 적합하다.
  • Jotai: 상태가 React 컴포넌트 내부에 한정되어 있으므로, React 외부에서는 접근이 불가능하다. 이 점 때문에 Jotai는 컴포넌트 내에서만 상태를 다루는 방식에 적합하다.

📌 하향식(top-down)과 상향식(bottom-up) 방식

  • Zustand: 하향식(top-down) 방식으로, 전체 애플리케이션에서 상태를 공유하고 관리하는 데 적합하다.
  • Jotai: 상향식(bottom-up) 방식으로, 각 컴포넌트에서 개별적으로 상태를 관리하며, 필요에 따라 다른 컴포넌트의 상태를 참조할 수 있다.

Daishi Kato(창시자)가 추천하는 선택 기준

📌 언제 어떤 것을 사용해야 하는가

  • useStateuseContext의 대체재가 필요하다면 Jotai가 잘 맞습니다.
  • 간단한 모듈 상태가 필요하다면 Zustand가 적합합니다.
  • 코드 분할이 중요하다면 Jotai가 좋은 성능을 발휘할 것입니다.
  • Redux devtools를 선호한다면 Zustand가 좋습니다.
  • Suspense를 활용하고 싶다면 Jotai가 적합합니다.
  • 새로운 것을 배우고 싶다면 둘 다 괜찮습니다.
  • Zustand를 좋아한다면 Jotai도 즐겁게 사용할 수 있습니다.
  • React 컨텍스트의 대안이 필요하다면 Jotai가 충분한 기능을 제공합니다.
  • React 외부에서 아톰을 읽고 쓰는 것이 필요하다면 Jotai는 스토어 API를 제공합니다.
  • 새로운 라이브러리를 만들고 싶다면 Jotai가 좋은 기본 요소를 제공할 수 있습니다.
  • 그렇지 않다면 두 라이브러리 모두 일반적인 목표와 기본 기술에 대해 상당히 유사하므로 둘 다 시도해 보고 피드백을 공유해 주세요.

참고 문헌

4개의 댓글

comment-user-thumbnail
2024년 10월 23일

좋은 글 감사드립니다 ! 글이 너무 읽기 쉽게 잘 작성되어있네요 👍
덕분에 좋-다이 쓰려구요 !

1개의 답글
comment-user-thumbnail
2024년 12월 13일

좋은 글 감사합니다! 두 라이브러리의 핵심 개념부터 용례까지 비교가 잘 되어 있어서 선택에 도움이 많이 되었어요 👍👍

1개의 답글