본문 바로가기

Web Development/Problem Solving

[Problem Solving] Zustand로 폼 상태 최적화하기: Context를 버리고 구독 범위를 좁히다

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로 한계에 부딪힌 폼 상태 관리에서 벗어나고 싶은 분들께 이 경험이 도움이 되었으면 합니다