콘텐츠로 이동

React 상태 관리

개요

앱이 커지면 상태를 어떻게 정리하고, 데이터가 컴포넌트 사이에서 어떻게 흐르는지를 의도적으로 설계해야 해요. 중복되거나 불필요한 상태는 버그의 흔한 원인인데, 특히 에디터처럼 같은 문서를 여러 화면 조각이 함께 보는 경우엔 이 기준이 바로 제품 품질로 이어져요. 이번엔 상태를 잘 짜는 법과 먼 컴포넌트끼리 상태를 나누는 법을 함께 봐요.

핵심 개념

React 공식 문서가 상태를 다루며 반복해 강조하는 원칙은 "상태에 중복·중복되지 않는 정보를 담지 말 것" 이에요. 상태가 하나 늘면 그걸 갱신하는 걸 잊어버리기 쉬워지고, 그게 버그가 되죠.

  • 불필요한 상태는 계산으로 대체 — 예를 들어 firstName, lastName을 저장하면서 fullName을 또 상태로 두면, 이름이 바뀔 때마다 fullName도 같이 갱신해야 해요. 대신 렌더 중에 firstName + ' ' + lastName으로 계산하면 상태가 하나 줄고 버그도 줄어요.
  • 상태 끌어올리기(Lifting State Up) — 두 컴포넌트의 상태가 항상 함께 변해야 한다면, 둘 다에서 상태를 지우고 가장 가까운 공통 부모로 끌어올린 뒤 props로 내려줘요. 한쪽만 활성화돼야 하는 아코디언 같은 UI가 전형적인 예시예요.
function Accordion() {
  // 활성 패널 번호를 부모가 관리하고, 자식은 props로 받는다.
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <Panel isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} />
  );
}
  • 상태 보존과 리셋 — 재렌더 때 React는 이전 트리와 "맞는" 부분은 보존해요. 하지만 대화 상대를 바꿨는데 입력값이 그대로 남는 것 같은 원치 않는 보존이 문제일 때, 다른 key를 넘기면 그 컴포넌트가 아예 다른 컴포넌트로 취급돼서 상태가 처음부터 다시 만들어져요. 예를 들어 <Chat key={to.email} />처럼요.
  • reducer로 로직 모아 두기 — 여러 이벤트 핸들러에 상태 갱신이 흩어지면 복잡해져요. 그럴 땐 갱신 로직을 컴포넌트 밖 reducer 하나에 모으고, 핸들러는 dispatch로 "무슨 일이 일어났는지"만 전달해요.
  • context로 깊이 전달 — props를 여러 단계를 거쳐 내려보내야 하거나 많은 컴포넌트가 같은 정보를 필요로 하면 불편해요. 그럴 땐 context로 부모가 트리 아래 어느 컴포넌트든 값을 제공할 수 있어요. reducer와 context를 결합하면 복잡한 화면의 상태 관리를 확장할 수 있어요.

실제 적용(데이터스케쳐스 관점)

데이터스케쳐스 화면은 "컴포넌트의 로컬 상태는 React가, 문서 전체의 공유 상태는 전역 store가" 맡는 이중 구조예요. React 공식 문서가 가르치는 로컬 상태 원칙 — 중복 제거, 공통 부모로 올리기, key로 리셋 — 은 에디터의 개별 위젯(입력 중인 노드, 임시 드래그 좌표)을 다룰 때 그대로 적용돼요.

반면 노드·연결 같은 문서 전역 상태는 여러 화면 조각이 함께 읽고 고쳐야 해서, React 자체의 props·context만으로는 프롭스 드릴링이 깊어져요. 그래서 Zustand 같은 전역 store가 이 책임을 나눠 가지죠. React의 context와 Zustand 모두 "전역 상태"라는 같은 문제를 푸는 도구지만, 규모와 갱신 빈도에 따라 선택이 갈려요. 캔버스처럼 수시로 변하는 공유 상태는 갱신이 잦은 만큼 store의 선택적 구독 구조가 React context보다 자주 어울려요.

더 알아보기