• Blog

  • Snippets

Comparison

June 4, 2024

Zustand

Zustand 공식문서를 참고하여 번역함.

Zustand는 React를 위한 많은 상태 관리 라이브러리 중 하나입니다. 이 페이지에서는 Redux, Valtio, Jotai, Recoil을 포함한 일부 라이브러리와의 비교를 통해 Zustand를 논의할 것입니다.

각 라이브러리에는 고유한 장점과 단점이 있으며, 우리는 각 라이브러리 간의 주요 차이점과 유사점을 비교할 것입니다.

# Redux

State Model (vs Redux)

개념적으로 Zustand와 Redux는 매우 유사하며, 둘 다 불변 상태 모델을 기반으로 합니다. 그러나 Redux는 애플리케이션을 컨텍스트 제공자(context providers)로 감싸야 하는 반면, Zustand는 그렇지 않습니다.

Zustand

import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
increment: (qty: number) => void
decrement: (qty: number) => void
}
const useCountStore = create<State & Actions>((set) => ({
count: 0,
increment: (qty: number) => set((state) => ({ count: state.count + qty })),
decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
}))
import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
increment: (qty: number) => void
decrement: (qty: number) => void
}
type Action = {
type: keyof Actions
qty: number
}
const countReducer = (state: State, action: Action) => {
switch (action.type) {
case 'increment':
return { count: state.count + action.qty }
case 'decrement':
return { count: state.count - action.qty }
default:
return state
}
}
const useCountStore = create<State & Actions>((set) => ({
count: 0,
dispatch: (action: Action) => set((state) => countReducer(state, action)),
}))

Redux

import { createStore } from 'redux'
import { useSelector, useDispatch } from 'react-redux'
type State = {
count: number
}
type Action = {
type: 'increment' | 'decrement'
qty: number
}
const countReducer = (state: State, action: Action) => {
switch (action.type) {
case 'increment':
return { count: state.count + action.qty }
case 'decrement':
return { count: state.count - action.qty }
default:
return state
}
}
const countStore = createStore(countReducer)
import { createSlice, configureStore } from '@reduxjs/toolkit'
const countSlice = createSlice({
name: 'count',
initialState: { value: 0 },
reducers: {
incremented: (state, qty: number) => {
// Redux Toolkit does not mutate the state, it uses the Immer library
// behind scenes, allowing us to have something called "draft state".
state.value += qty
},
decremented: (state, qty: number) => {
state.value -= qty
},
},
})
const countStore = configureStore({ reducer: countSlice.reducer })

Render Optimization (vs Redux)

애플리케이션 내에서 렌더링 최적화를 할 때, Zustand와 Redux 간의 접근 방식에는 큰 차이가 없습니다. 두 라이브러리 모두 셀렉터를 사용하여 수동으로 렌더링 최적화를 적용하는 것을 권장합니다.

Zustand

import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
increment: (qty: number) => void
decrement: (qty: number) => void
}
const useCountStore = create<State & Actions>((set) => ({
count: 0,
increment: (qty: number) => set((state) => ({ count: state.count + qty })),
decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
}))
const Component = () => {
const count = useCountStore((state) => state.count)
const increment = useCountStore((state) => state.increment)
const decrement = useCountStore((state) => state.decrement)
// ...
}

Redux

import { createStore } from 'redux'
import { useSelector, useDispatch } from 'react-redux'
type State = {
count: number
}
type Action = {
type: 'increment' | 'decrement'
qty: number
}
const countReducer = (state: State, action: Action) => {
switch (action.type) {
case 'increment':
return { count: state.count + action.qty }
case 'decrement':
return { count: state.count - action.qty }
default:
return state
}
}
const countStore = createStore(countReducer)
const Component = () => {
const count = useSelector((state) => state.count)
const dispatch = useDispatch()
// ...
}
import { useSelector } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'
import { createSlice, configureStore } from '@reduxjs/toolkit'
const countSlice = createSlice({
name: 'count',
initialState: { value: 0 },
reducers: {
incremented: (state, qty: number) => {
// Redux Toolkit does not mutate the state, it uses the Immer library
// behind scenes, allowing us to have something called "draft state".
state.value += qty
},
decremented: (state, qty: number) => {
state.value -= qty
},
},
})
const countStore = configureStore({ reducer: countSlice.reducer })
const useAppSelector: TypedUseSelectorHook<typeof countStore.getState> =
useSelector
const useAppDispatch: () => typeof countStore.dispatch = useDispatch
const Component = () => {
const count = useAppSelector((state) => state.count.value)
const dispatch = useAppDispatch()
// ...
}

# Valtio

State Model (vs Valtio)

Zustand와 Valtio는 상태 관리 접근 방식이 근본적으로 다릅니다. Zustand는 불변(immutable) 상태 모델을 기반으로 하는 반면, Valtio는 가변(mutable) 상태 모델을 기반으로 합니다.

Zustand

import { create } from 'zustand'
type State = {
obj: { count: number }
}
const store = create<State>(() => ({ obj: { count: 0 } }))
store.setState((prev) => ({ obj: { count: prev.obj.count + 1 } }))

Valtio

import { proxy } from 'valtio'
const state = proxy({ obj: { count: 0 } })
state.obj.count += 1

Render Optimization (vs Valtio)

Zustand와 Valtio의 또 다른 차이점은 Valtio는 속성 접근을 통해 렌더링 최적화를 수행한다는 것입니다. 그러나 Zustand에서는 셀렉터를 사용하여 수동으로 렌더링 최적화를 적용하는 것이 권장됩니다.

Zustand

import { create } from 'zustand'
type State = {
count: number
}
const useCountStore = create<State>(() => ({
count: 0,
}))
const Component = () => {
const count = useCountStore((state) => state.count)
// ...
}

Valtio

import { proxy, useSnapshot } from 'valtio'
const state = proxy({
count: 0,
})
const Component = () => {
const { count } = useSnapshot(state)
// ...
}

# Jotai

State Model (vs Jotai)

Zustand와 Jotai 사이에는 두 가지 주요 차이점이 있습니다. 첫째, Zustand는 단일 스토어를 사용하는 반면, Jotai는 함께 조합할 수 있는 원자(atom)들로 구성됩니다. 둘째, Zustand 스토어는 외부 스토어이기 때문에 React 외부에서의 접근이 필요한 경우에 더 적합합니다.

Zustand

import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
updateCount: (
countCallback: (count: State['count']) => State['count'],
) => void
}
const useCountStore = create<State & Actions>((set) => ({
count: 0,
updateCount: (countCallback) =>
set((state) => ({ count: countCallback(state.count) })),
}))

Jotai

import { atom } from 'jotai'
const countAtom = atom<number>(0)

Render Optimization (vs Jotai)

Jotai는 원자(atom) 의존성을 활용하여 렌더링 최적화를 이루어냅니다. 반면에, Zustand에서는 셀렉터를 사용하여 수동으로 렌더링 최적화를 적용하는 것이 권장됩니다.

Zustand

import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
updateCount: (
countCallback: (count: State['count']) => State['count'],
) => void
}
const useCountStore = create<State & Actions>((set) => ({
count: 0,
updateCount: (countCallback) =>
set((state) => ({ count: countCallback(state.count) })),
}))
const Component = () => {
const count = useCountStore((state) => state.count)
const updateCount = useCountStore((state) => state.updateCount)
// ...
}

Jotai

import { atom, useAtom } from 'jotai'
const countAtom = atom<number>(0)
const Component = () => {
const [count, updateCount] = useAtom(countAtom)
// ...
}

# Recoil

State Model (vs Recoil)

Zustand와 Recoil의 차이점은 Zustand와 Jotai의 차이점과 유사합니다. Recoil은 원자 객체 참조 아이디 대신 원자 문자열 키에 의존합니다. 또한, Recoil은 애플리케이션을 컨텍스트 제공자(context provider)로 감싸야 합니다.

Zustand

import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
setCount: (countCallback: (count: State['count']) => State['count']) => void
}
const useCountStore = create<State & Actions>((set) => ({
count: 0,
setCount: (countCallback) =>
set((state) => ({ count: countCallback(state.count) })),
}))

Recoil

import { atom } from 'recoil'
const count = atom({
key: 'count',
default: 0,
})

Render Optimization (vs Recoil)

이전 최적화 비교와 마찬가지로, Recoil은 원자(atom) 의존성을 통해 렌더링 최적화를 수행합니다. 반면, Zustand에서는 셀렉터를 사용하여 수동으로 렌더링 최적화를 적용하는 것이 권장됩니다.

Zustand

import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
setCount: (countCallback: (count: State['count']) => State['count']) => void
}
const useCountStore = create<State & Actions>((set) => ({
count: 0,
setCount: (countCallback) =>
set((state) => ({ count: countCallback(state.count) })),
}))
const Component = () => {
const count = useCountStore((state) => state.count)
const setCount = useCountStore((state) => state.setCount)
// ...
}

Recoil

import { atom, useRecoilState } from 'recoil'
const countAtom = atom({
key: 'count',
default: 0,
})
const Component = () => {
const [count, setCount] = useRecoilState(countAtom)
// ...
}

# Npm Downloads Trend

Npm Downloads Trend of State Management Libraries for React