React 상태 관리 가이드( JUSTAND )
개요
상태관리 라이브러리는 왜 필요한가?
React의 기본 한계
- Prop Drilling: 깊은 컴포넌트에 데이터 전달 시 중간 컴포넌트들을 모두 거쳐야 함
- 전역 상태 관리 어려움: 여러 컴포넌트가 공유하는 상태 관리가 복잡
- 상태 변화 추적 곤란: useState로 여기저기서 setState 호출 시 어디서 상태가 변경되었는지 추적 어려움(집중화해서 관리하면 해당 위치에서만 상태가 업데이트 - 집중화, 목적을 명확하게 사용 )
위와 같은 문제로 나는 주로 justand + React-query를 사용한다.
react-query로 서버에서 오는 데이터를 관리해서 SSR에서도 데이터를 관리하고
Justand로 클라이언트 상태를 관리한다.
justand는 Redux-devtools
을 지원해서 상태 변화를 시간순으로 추적하여 디버깅 편리하다.
상태 분류 및 해결 방법
1. 전역 상태 → Zustand
const useUserStore = create((set) => ({
user: null,
login: (user) => set({ user, isLoggedIn: true }),
logout: (user) => set({ user, isLoggedIn: false }),
}));
- 적용 대상: 사용자 정보, 장바구니, 테마, 로그인 상태 등
- 특징: 여러 페이지에서 공유, 새로고침해도 유지 필요
// 유지를 하기위해서는 persist 옵션을 사용해야한다.
const useStore = create(
persist(
// 첫 번째 인자: 일반적인 Zustand store 함수
(set, get) => ({
user: null,
cart: [],
login: (userData) => set({ user: userData }),
addToCart: (item) => set((state) => ({
cart: [...state.cart, item]
})),
}),
// 두 번째 인자: persist 설정 객체 / localsotorage의 app-storage로 저장됨
{
name: 'app-storage', // localStorage 키 이름
partialize: (state) => ({}), // 어떤 상태를 저장할지
storage: createJSONStorage(() => localStorage), // 저장소 타입
}
)
);
2. 페이지별 복잡한 상태 → Context API
전역으로 쓸 요소는 아닌데 현재 페이지에서 사용해야하는 전역변수가 있다. 예를들면 복잡한 Canvas화면에서 동작을 할 때 전체 요소를 중앙에서 관리해야하는 경우가 있다. 해당 케이스를 위해서는 Context API를 사용해서 해당 페이지만의 상태를 관리하는게 좋다.
Atomic Design Pattern에서 복잡한 Organisms이나 templates에서도 사용된다.
const CheckoutPageContext = createContext();
function CheckoutPageProvider({ children }) {
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState({});
return (
<CheckoutPageContext.Provider value={{ currentStep, formData, ... }}>
{children}
</CheckoutPageContext.Provider>
);
}
- 적용 대상: 특정 페이지의 폼 데이터, 단계 상태, 모달 상태 등
- 특징: 해당 페이지에서만 사용, props drilling 방지
SSR을 사용하는 환경(Next.js)에서의 주의사항
서버에서 받아오는 중요데이터는
// ✅ 서버 상태 - TanStack Query/SWR
const { data: user } = useQuery('/api/user');
// ✅ 클라이언트 상태 - Zustand (SSR 안전 패턴)
const useClientStore = create((set) => ({
theme: null, // 초기값 null
isHydrated: false,
hydrate: () => {
const savedTheme = localStorage.getItem('theme') || 'light';
set({ theme: savedTheme, isHydrated: true });
},
}));
// 컴포넌트에서 안전하게 사용
function Header() {
const { theme, isHydrated } = useClientStore();
const currentTheme = isHydrated ? theme : 'light'; // 기본값 제공
return <header className={currentTheme}>...</header>;
}
🎯 최종 권장 패턴
상태 타입 | 도구 | 사용 사례 |
---|---|---|
전역 상태 | Zustand | user, cart, theme, authToken |
서버 상태 | TanStack Query | API 데이터, 캐싱 |
페이지별 복잡한 상태 | Context API | 폼, 단계, props drilling 해결 |
간단한 로컬 상태 | useState | 모달, 입력값, 토글 |
핵심: 상태의 범위와 복잡도에 따라 적절한 도구를 선택하고, Next.js에서는 서버/클라이언트 상태를 명확히 구분하여 사용하자!