문제점 선정 이유
프로젝트를 처음 진행할 당시엔 상태 관리에 대한 이해가 깊지 않아 그냥 주변에서 많이 쓰는 라이브러리를 선택해 개발을 진행했습니다. 큰 문제는 없었지만 리팩토링 시점에 다시 코드를 들여다보니 “이 상태, 꼭 이렇게 관리해야 할까?”라는 의문이 들기 시작했습니다. 결국 단순히 잘 알려진 도구를 쓰는 것보다, 상태의 복잡도와 사용 패턴에 따라 적절한 도구를 선택하는 것이 더 중요하다는 걸 느끼게 되었습니다. 해당 과정에서 Recoil로 관리하고 있던 상태를 실제로 Zustand로 옮긴다면 어떤 구조로 바꿔야 할지도 고민하게 되었고 이 글에서는 그런 고민과 시도, 그리고 배운 점들을 담아보려 합니다.
상태 관리가 필요한 이유
상태란?
상태는 애플리케이션의 현재 모습을 결정하는 데이터를 의미합니다. 즉, 사용자가 어떤 행동을 하느냐에 따라 이 상태가 변하게됩니다.
상태 관리의 필요성
1. 데이터 일관성 유지
애플리케이션은 여러 컴포넌트에서 동일한 데이터를 공유할 때가 많습니다. 그런데 만약 데이터가 서로 불일치한다면 사용자는 혼란을 느끼게 됩니다. 이때 상태 관리는 앱 전체에서 같은 정보를 공유하게 만들어 일관된 사용자 경험을 보장할 수 있도록 합니다.
2. 복잡한 컴포넌트 구조에서 효율적인 데이터 전달
상태 관리가 없으면 부모에서 자식 컴포넌트로 데이터를 계속 넘겨야 하는 props drilling 문제가 발생합니다. 중앙화된 상태 관리 시스템은 필요한 컴포넌트가 직접 상태에 접근할 수 있게 해주어 불필요한 props 전달을 줄입니다.
3. 사용자 경험 향상
상태 관리는 사용자가 애플리케이션을 사용하는 동안 일관된 경험을 제공합니다. 상태 관리를 통해 앱은 사용자의 이전 작업과 선호도를 기억하고, 페이지 간 이동 시에도 작업 내용이 유지됩니다. 이는 로딩 시간을 줄이고 즉각적인 반응성을 제공하여 전반적인 사용자 만족도를 높입니다.
그럼, React에서 왜 상태가 중요한 것일까??
Vanilla JavaScript에서는 보통 버튼 클릭 같은 이벤트가 발생하면, 직접 DOM을 조작해서 화면을 바꿔야 합니다. 하지만 이 방식은 코드가 길어지고 여러 요소가 복잡하게 얽히면 실수하기 쉽습니다. React는 이런 번거로운 DOM 조작을 자동으로 처리해주고, 개발자는 ‘상태(state)’를 어떻게 관리할지에만 집중하면 됩니다.
예를 들어 쇼핑몰에서 '장바구니에 추가' 버튼을 눌렀는데, 장바구니 수량이 바로 반영되지 않는다면 사용자 입장에선 굉장히 불편을 겪게됩니다. 이때, React에서는 상태만 잘 업데이트하면 화면은 자동으로 최신 상태로 바뀝니다. 즉, React에서 상태 관리는 사용자에게 항상 신뢰할 수 있는 화면을 보여주는 핵심 역할을 합니다.
상태 관리 라이브러리 종류
Redux


Redux는 Flux 아키텍처를 기반으로 설계된 상태 관리 라이브러리입니다.
중앙 저장소인 Store를 통해 애플리케이션 전역의 상태를 한 곳에서 관리할 수 있도록 도와줍니다.
특징
- 모든 상태는 Store에 저장되며, 상태를 변경할 때는 반드시 Action과 Reducer를 거쳐야 합니다.
이 과정을 통해 상태의 변경 흐름이 명확하게 추적됩니다. - Redux는 상태를 불변성을 유지한 채 새롭게 생성합니다. 이 덕분에 상태 변화가 예측 가능하고, 디버깅도 훨씬 수월해집니다.
- 비동기 작업은 redux-thunk, redux-saga 같은 Middleware를 통해 처리할 수 있습니다.
Recoil

Recoil은 Facebook에서 개발한 React 전용 상태 관리 라이브러리입니다.
React의 자연스러운 동작과 잘 통합되며, 상태를 atom 단위로 정의해 필요한 곳에서 구독하고 업데이트 가능합니다.
특징
- Atom: 상태의 최소 단위를 의미하며, React 컴포넌트에서 useRecoilState를 통해 읽고 업데이트할 수 있습니다.
- Selector: Atom 상태로부터 파생된 상태를 계산하고 이를 효율적으로 관리가 가능합니다.
- Recoil은 React의 컴포넌트 기반 접근 방식과 잘 맞아 상태 관리가 간단하며 각 컴포넌트에서 필요한 상태만 구독하게 할 수 있어 불필요한 렌더링도 줄일 수 있습니다.
- ❗️Recoil은 한때 React 전용 상태 관리 라이브러리로 주목받았지만 2023년 이후 업데이트가 점점 줄어들기 시작했고 현재는 개발이 중단된 상태입니다.
Zustand

Zustand는 비교적 가벼운 상태 관리 라이브러리로, Flux 패턴을 기반으로 한 설계 방식을 채택하고 있습니다.
특징
- 상태와 상태를 업데이트하는 로직을 Store를 통해 간단하게 정의하고, 이를 기반으로 상태를 효율적으로 관리합니다.
- Pub/Sub 모델을 사용하여 상태 변경 시 필요한 컴포넌트만 업데이트합니다.
- create 메서드를 사용해 간단하게 상태와 업데이트 로직을 정의할 수 있습니다.
프로젝트에서 했던 상태 관리
기존 Recoil 코드 살펴보기
//paymentAtom.ts
import { atom } from "recoil";
// 결제 데이터 인터페이스
export interface PaymentData {
storeName: string;
paymentDate: string;
paidAmount: string;
onSiteAmount: string;
}
// 결제 관련 데이터를 관리하는 atom
export const paymentAtom = atom<PaymentData | null>({
key: "paymentData", // 고유한 키
default: null, // 초기 값은 null로 설정
});
payment에서 Index.tsx
export default function Payment() {
// Recoil 상태와 결제 데이터 설정
const [paymentData, setPaymentData] = useRecoilState(paymentAtom);
const navigate = useNavigate();
key 값과 상태 변수명
- 통일된 네이밍이 유지보수하기 더 쉬워서 같은 이름을 사용하는 게 일반적입니다.
- key와 상태 변수명은 동일하게 통일하는 것이 유지보수와 코드 이해에 도움이 됩니다.
해당 부분을 zustand로 바꿔보기
Zustand Store 생성 (atoms/paymentStore.ts)
Recoil에서는 atom을 사용했지만, Zustand에서는 create를 이용해서 상태를 만듭니다.
import { create } from "zustand";
// 결제 데이터 타입
interface PaymentData {
storeName: string;
paymentDate: string;
paidAmount: string;
onSiteAmount: string;
}
// Zustand Store 생성
interface PaymentStore {
paymentData: PaymentData | null;
setPaymentData: (data: PaymentData) => void;
}
export const usePaymentStore = create<PaymentStore>((set) => ({
paymentData: null, // 초기값
setPaymentData: (data) => set({ paymentData: data }),
}));
- usePaymentStore를 만들어서 전역 상태를 관리
- setPaymentData 함수를 만들어서 상태를 업데이트
//return은 동일하게 이루어짐!
export default function Payment() {
// Zustand 상태 가져오기
const { paymentData } = usePaymentStore();
// 최적화된 상태 구독 (필요한 값만 가져옴)
const storeName = usePaymentStore((state) => state.paymentData?.storeName);
const paymentDate = usePaymentStore((state) => state.paymentData?.paymentDate);
const paidAmount = usePaymentStore((state) => state.paymentData?.paidAmount);
const onSiteAmount = usePaymentStore((state) => state.paymentData?.onSiteAmount);
그럼 지금 저 Payment atom은 recoil이 적합할까 아니면 zustand가 적합한것인가???
Recoil의 경우
- Recoil을 사용하려면 최상위 컴포넌트를 <RecoilRoot>로 감싸줘야 합니다.
그렇지 않으면 useRecoilState를 사용할 수 없습니다.
import { createRoot } from "react-dom/client";
import { router } from "./router/router";
import { RouterProvider } from "react-router-dom";
import GlobalStyle from "./style/global-style";
import { RecoilRoot } from "recoil";
createRoot(document.getElementById("root")!).render(
<RecoilRoot>
<GlobalStyle />
<RouterProvider router={router} />
</RecoilRoot>,
);
- useRecoilState를 사용할 때는 상태 변경 시 관련 컴포넌트가 전체 렌더링될 가능성이 있습니다.
⇒ 따라서 렌더링 최적화를 별도로 신경 써야 합니다. - selector를 통해 최적화를 할 수 있으나 구현이 다소 복잡해질 수 있습니다.
- 컴포넌트 간 상태 공유가 많고 복잡한 애플리케이션에서는 Recoil이 더 적합할 수 있습니다.
Zustand의 경우
- Recoil과 달리 최상위 컴포넌트를 감싸줄 필요가 없습니다.
- useStore 훅을 통해 필요한 상태만 선택적으로 구독할 수 있어 렌더링 최적화에 매우 강력합니다.
⇒ 필요한 데이터만 가져오기 때문에 불필요한 리렌더링을 줄일 수 있습니다. - 한 곳에서 전역 상태를 읽기만 하거나 상태 관리가 비교적 단순한 경우에는 Zustand가 더 적합할 수 있습니다.
결론
프로젝트에서는 Payment 상태 관리의 경우 단순한 데이터 구조와 제한된 범위의 상태 공유가 필요했기 때문에 Zustand가 더 적합했습니다. 특히 불필요한 Provider 없이 간결한 코드를 유지하면서도 선택적 구독을 통한 렌더링 최적화를 쉽게 달성할 수 있었습니다. 이번 리팩토링을 통해 상태 관리 도구는 사용 패턴과 복잡도에 따라 유연하게 선택적으로 적용하는 것이 중요하다는 점을 깨달았습니다.
또한 ,상태 관리 라이브러리 선택 시에는 기술적 특성뿐만 아니라 유지보수 상태도 중요한 고려사항임을 알게 되었습니다. Zustand는 최근 간결한 API와 타입스크립트 지원, 그리고 React 18과의 호환성 측면에서 장점이 많아 장기적으로도 안정적인 선택이 될 수 있습니다. 앞으로의 프로젝트에서도 단순히 트렌드를 따르기보다는, 프로젝트의 구체적인 요구사항과 라이브러리의 지속 가능성을 함께 고려해 가장 적합한 상태 관리 방식을 선택해야 한다고 생각합니다.
'refactor' 카테고리의 다른 글
| [반려견 미용 견적 서비스] refactor4 - 로그인 로직 (4) | 2025.06.20 |
|---|---|
| [반려견 미용 견적 서비스] refactor 2 - 스켈레톤 UI (1) | 2025.04.28 |
| [반려견 미용 견적 서비스] refactor 1 - SEO 설정 (0) | 2025.04.19 |
| [반려견 미용 견적 서비스] PEAUTY 회고 및 리팩토링 방향성 (0) | 2025.04.16 |