React로 복잡한 폼 페이지를 만들다 보면 점점 많은 상태들이 생겨나게 됩니다.
이 글은 프로젝트 초기에 설계했던 상태 관리 방식에서 출발해, 기능 고도화로 인한 문제를 어떻게 해결했는지,
그리고 Zustand를 활용한 최적화 과정과 결과를 공유하는 글입니다.
초기 설계: useFormDatas로 상태 통합 관리
프로젝트 초반에는 useFormDatas
라는 커스텀 훅을 통해 모든 폼 상태를 한 곳에서 통합적으로 관리했습니다. 이 방식의 장점은 다음과 같았습니다:
- 상태 흐름을 추적하기 쉬움
- 하나의 파일로 팀원 간 이해가 쉬움
- 유지보수 용이
// form과 관련된 모든 데이터를 저장하는 커스텀 훅
export const useFormDatas = () => {
const [formData, setFormData] = useState({
name: '',
phoneNumber: '',
email: '',
font: 'Pretendard',
agreementChecked: false,
primaryColor: '#C4D2FF',
modules: []
});
const addModule = (option: string, type: string): Module => {
// ... 로직
return newModule;
};
return {
formData,
setFormData,
addModule,
// ...기타 상태 처리 메서드들
}
};
문제 발생: 상태 증가에 따른 커스텀 훅의 비대화
하지만 시간이 지나며 기능이 고도화되었고, 다음과 같은 상태가 추가되었습니다:
- 폼 작성 단계
- 선택된 요소들
- 디스플레이 상태 (ex. 모듈 표시 여부)
- 파일 첨부
- 색상 변경
그 결과:
- 훅 내부 로직이 복잡해지면서 가독성이 저하됨
- 책임이 분리되지 않아 구조가 불명확해짐
- 상위 컴포넌트에서 자식 컴포넌트로 props를 과도하게 전달해야 함
- 최상위에서 가져온 상태가 변경되면 하위 컴포넌트 전체가 재렌더링됨
고민: 어떤 상태 관리 방식이 최적일까?
1. React Context API
처음에는 Context를 활용해 상태를 단계별로 쪼개보려 했습니다. 하지만 이 방식에는 치명적인 단점이 있었습니다:
Context는 기본적으로 Provider 내부에 있는 모든 컴포넌트를 다시 렌더링한다.
2. Zustand + 구독 분리
Zustand는 아래와 같은 이유로 적합하다고 판단했습니다:
- 각 컴포넌트가 필요한 상태만 구독 가능 (selector 기반)
- 별도의 Provider 없이 사용 가능
- 상태 분리가 용이하며 테스트도 편함
실제 구현: Zustand로 폼 상태 구조화
상태 구조를 \"기능 단위\"로 나누고, 각 컴포넌트에서 필요한 selector만 구독하도록 했습니다. 또한, 정적인 이미지 카드 등에서는 React.memo
를 활용하여 불필요한 리렌더링을 방지했습니다.
const useFormStore = create((set) => ({
name: '',
setName: (name) => set({ name }),
// 모듈 관련 상태
modules: [],
addModule: (module) => set((state) => ({
modules: [...state.modules, module]
})),
// 색상 등 기타 상태
primaryColor: '#C4D2FF',
setPrimaryColor: (color) => set({ primaryColor: color })
}));
컴포넌트에서는 이렇게 사용합니다:
const name = useFormStore((state) => state.name);
const setName = useFormStore((state) => state.setName);
최적화 결과 비교
✅ 최적화 전
- 버튼 하나만 클릭해도 전체 컴포넌트가 재렌더링됨
- 반응성이 떨어지고 깜빡임이 발생함
✅ 최적화 후
- 변경된 상태와 관련된 부분만 재렌더링됨
- 사용자 경험 향상, 반응 속도 향상
회고: 언제, 얼마나 쪼갤 것인가?
Zustand는 확실히 가볍고 효율적인 상태 관리 도구입니다.
다만 상태를 너무 잘게 쪼개려다보면 오히려 코드 복잡도를 높이고 협업에 방해가 될 수 있다는 생각이 들었습니다.
그래서 저는 아래의 방식으로 적당한 선을 찾아서 상태를 쪼갰습니다.
- 자주 변하지 않는 컴포넌트는
memo
로 감싸기, - 자주 바뀌는 상태는 Zustand의 selector로 구독
마무리
Zustand를 도입하며 얻은 가장 큰 수확은 책임이 분리된 깔끔한 구조와 정확한 재렌더링 제어였습니다.
Context로 한계에 부딪힌 폼 상태 관리에서 벗어나고 싶은 분들께 이 경험이 도움이 되었으면 합니다
'Web Development > Problem Solving' 카테고리의 다른 글
[Problem Solving] useSelector, 커스텀 훅, 그리고 리렌더링 최적화 (0) | 2025.05.07 |
---|---|
[Problem Solving] 프론트엔드 성능 최적화 기본 개념 (1) | 2025.05.06 |
[Web] Light house를 이용한 페이지 최적화 (1) | 2024.05.29 |
[Web] 페이지에 잔상이 남는 현상 처리 (1) | 2024.05.22 |
[Web] dangerouslySetInnerHTML 사용 시 나타날 수 있는 문제점 (1) | 2024.05.22 |