보통 전역 상태 관리로 많이 사용하는 라이브러리는 Redux 이다.
npm trends 사이트에 들어가서 실제로 검색해보면 전역상태 관리 툴 사용량에서 Redux 가 압도적으로 많은걸 볼 수 있다.
실제로 리액트 강의를 처음 접하거나 공부하기 시작할때 접하게 되는게 대부분 Redux고, 본인도 지금까지 그 절차를 따라왔다.
공부 뿐만 아니라 실제로 구인구직 사이트들에서 회사들이 사용하는 상태관리 툴들을 보면 Redux toolkit 이 굉장히 많은 것을 볼수 있다.
하지만 이번에 프로젝트를 진행하면서 Redux 가 아닌 Zustand로 라이브러리를 변경하게 되었다.
실 예의 코드로 바로 비교해보겠다.
// provider 선언
"use client";
import React from "react";
import { Provider } from "react-redux";
import { store } from "./store";
type Children = {
children: React.ReactNode;
};
export default function Providers({ children }: Children) {
return (
<>
<Provider store={store}>{children}</Provider>
</>
);
}
// 슬라이스 만들기
export const themeSlice = createSlice({
name: "theme",
initialState,
reducers: {
setCurrentTheme: (state, action) => {
state.currentTheme = action.payload;
},
},
});
export const selectCurrentTheme = (state: { theme: ThemeState }) =>
state.theme.currentTheme;
export const { setCurrentTheme } = themeSlice.actions;
//스토어 만들어주기
import { themeSlice } from "./features/themeSlice";
export const store = configureStore({
reducer: {
theme: themeSlice.reducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// dispatch, useSelector 훅 생성
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
테마 값 하나 사용하고 싶을뿐인데, 준비할게 너무 많다.
이제 Zustand 로 한번 똑같이 전역 테마 상태값을 설정해보자.
// 스토어 생성
import { create } from 'zustand';
type State = {
theme: boolean;
updateTheme: (theme: boolean) => void;
};
const useThemeStore = create<State>((set) => ({
theme: false,
updateTheme: (theme) => set({ theme }),
}));
export default useThemeStore;
// 사용하고 싶은 곳에서 불러와서 업데이트
const { updateTheme } = useThemeStore();
useEffect(() => {
updateTheme(darkTheme);
}, [darkTheme, updateTheme]);
차이가 확연히 드러난다.
일단 기본적으로 Zustand는 리덕스와 다르게 Provider를 설정해줄 필요가 없다.
Redux는 Context API를 통해 트리구조를 형성해서 그 안에 포함되어있는 컴포넌트들에서 상태값들이 사용이 가능하다.
하지만 Zustand 는 아예 컴포넌트 트리 밖에 존재하고 상태의 특정 부분만 구독하는 기능을 제공한다.
그러므로 불필요한 리렌더링을 방지하고 더 간편한 보일러 플레이트를 제공한다.
그렇다고 무조건적으로 zustand 가 리렌더링을 최적화 해주는것은 아니다. 중첩된 객체인 경우 shallow copy 를 방지하기 위해서 immer 미들웨어를 제공해주고 있다.
공식 문서 링크
하지만 이렇다고 해서 무조건적으로 Redux를 지양해야할 필요는 없다고 생각한다.
아직도 리덕스의 사용량은 압도적으로 많기도 하고 관리해야할 상태값이 복잡해지면 복잡해질수록 Zustand도 허들이 높아지긴 마찬가지다.
하지만 프로젝트를 진행하면서 관리해야할 상태 값들의 형태가 복잡한 형태는 아니었기에, 더 간편한 Zustand로 마이그레이션을 했다.
jotai 도 설치해서 사용해보았는데, zustand 보다 훨씬 더 간편했다.
스토어를 만들어서 액션을 통해 상태를 업데이트하는 flux 패턴인 Redux, zustand와 달리, jotai는 아예 외부에서 원자 같은 상태 하나하나로 존재하는 atomic 패턴이었다.
// 상태 만들어주기
import { atom } from 'jotai';
export const themeAtom = atom<boolean>(false);
// 사용하고 싶은 컴포넌트에 상태값 업데이트
const setDarkMode = useSetAtom(themeAtom);
useEffect(() => {
setDarkMode(darkTheme);
}, [darkTheme, setDarkMode]);
하지만 이 상태값을 사용할때마다 ""Detected multiple Jotai instances. It may cause unexpected behavior with the default store" 라는 에러가 계속 발생했다.
에러 메세지를 보면 Provider로 default store를 선언해주면 발생한다는데, 선언한적이 없는데도 계속 이런 에러가 발생했다.
discussion 에서도 글을 남겨보았지만, 메인 컨테이너도 정확한 답변을 주지 못해서 프로덕션 환경까지는 갖고가기에 무리일것 같다는 판단이 들어서 Zustand로 다시 회귀했다.
아직 Zustand에 대해서 깊이 아는것도 아니고, 상태 값들을 더 주입해야하지만 한번 도전해볼만한 라이브러리라고 생각한다.