React state와 props 가이드: 데이터 흐름을 이해하는 가장 쉬운 방법
Web

React state와 props 가이드: 데이터 흐름을 이해하는 가장 쉬운 방법


리액트를 처음 공부할 때 JSX보다 더 헷갈리는 부분이 stateprops입니다. 둘 다 화면을 바꾸는 데 쓰이는 것처럼 보이지만, 실제로는 역할이 꽤 다릅니다.

이 둘을 제대로 이해하지 못하면 입력값이 어디서 바뀌는지 추적하기 어려워지고, 부모와 자식이 서로 같은 데이터를 중복으로 들고 있게 됩니다. 그러면 작은 화면도 금방 복잡해집니다.

이 글에서는 아래 내용을 정리합니다.

  • stateprops의 차이
  • 데이터를 어디에 두는 것이 자연스러운지
  • state를 끌어올려야 하는 상황과 피해야 할 상황

핵심은 props는 전달받는 값이고, state는 컴포넌트가 직접 관리하는 값이라는 점입니다.

React에서 state와 props는 무엇인가

props는 부모가 자식에게 넘겨주는 입력값입니다. 자식 입장에서는 읽을 수는 있지만 직접 바꾸지는 않습니다.

state는 컴포넌트 내부에서 관리하는 값입니다. 사용자 입력이나 이벤트에 따라 직접 변경되며, 바뀌면 화면이 다시 렌더링됩니다.

아래 예제를 보면 차이가 더 명확해집니다.

function Parent() {
  return <Counter initialCount={0} />;
}

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

여기서 initialCount는 props이고, count는 state입니다. 부모는 시작값을 전달했고, 실제로 바뀌는 값은 Counter가 내부에서 관리합니다.

언제 state를 둬야 할까

아래 질문에 해당하면 state일 가능성이 큽니다.

  • 사용자 액션으로 값이 바뀌는가?
  • 시간이 지나면서 값이 달라지는가?
  • 같은 컴포넌트 안에서 화면을 다시 그리게 만드는가?

예를 들면 이런 값들이 state에 가깝습니다.

  • 입력창 값
  • 모달 열림 여부
  • 탭 선택 상태
  • 로딩 여부
  • 필터 조건

반대로 서버에서 받아온 값이지만 이 컴포넌트에서 직접 바꾸지 않는다면, 꼭 local state로 옮기지 않아도 됩니다.

언제 props로 내려야 할까

부모가 이미 알고 있는 데이터를 자식이 표현만 하면 props로 넘기는 편이 자연스럽습니다.

예를 들어 사용자 카드가 있다고 하면 아래처럼 설계할 수 있습니다.

function UserList({ users }) {
  return (
    <ul>
      {users.map((user) => (
        <UserCard key={user.id} user={user} />
      ))}
    </ul>
  );
}

UserCarduser를 전달받아 렌더링만 합니다. 이럴 때 자식 안에서 다시 useState(user)를 만들면 데이터가 이중화될 수 있습니다.

state를 끌어올려야 하는 상황

두 개 이상의 자식 컴포넌트가 같은 데이터를 함께 써야 한다면, 공통 부모로 state를 올리는 것이 기본 해법입니다. 이를 흔히 lifting state up이라고 부릅니다.

예를 들어 검색 입력창과 결과 목록이 같은 검색어를 공유해야 한다면 아래처럼 구성할 수 있습니다.

function SearchPage() {
  const [keyword, setKeyword] = useState('');

  return (
    <>
      <SearchInput keyword={keyword} onChange={setKeyword} />
      <SearchResults keyword={keyword} />
    </>
  );
}

이 구조의 장점은 데이터의 진짜 출처가 하나라는 점입니다. 검색어가 어디서 바뀌는지 추적하기 쉬워지고, 서로 다른 자식이 엇갈린 값을 가지는 문제도 줄어듭니다.

state를 너무 많이 올리면 생기는 문제

초보자에게 자주 보이는 실수는 모든 state를 상위 컴포넌트에 몰아넣는 것입니다. 물론 공유가 필요한 값은 올려야 하지만, 오직 한 컴포넌트 안에서만 쓰이는 값까지 올릴 필요는 없습니다.

예를 들어 AccordionItem 내부의 열림 상태가 다른 곳과 공유되지 않는다면 그 안에 두는 편이 더 단순합니다.

function AccordionItem({ title, children }) {
  const [open, setOpen] = useState(false);

  return (
    <section>
      <button onClick={() => setOpen(!open)}>{title}</button>
      {open && <div>{children}</div>}
    </section>
  );
}

state를 올릴지 말지는 “누가 이 값을 정말 알아야 하는가”를 기준으로 판단하면 좋습니다.

자주 하는 실수

1. props를 state로 복사해두기

초기값이 필요하다는 이유로 props를 그대로 state에 복사하면 동기화 문제가 생기기 쉽습니다. 정말 편집용 임시 상태가 필요한 상황이 아니라면 피하는 편이 안전합니다.

2. 같은 데이터를 여러 곳에서 각각 관리하기

카트 개수, 선택된 사용자, 검색어처럼 핵심 데이터가 여러 컴포넌트에 중복되면 버그가 생기기 쉽습니다.

3. state 변경 책임이 불분명한 구조 만들기

누가 setState를 호출하는지 너무 멀리 퍼져 있으면 디버깅이 어려워집니다. 이벤트 흐름은 가능한 짧고 명확할수록 좋습니다.

공부할 때 바로 해볼 연습

아래처럼 작은 투두 앱을 만든다고 가정해보세요.

  • 입력창의 현재 값
  • 할 일 목록
  • 완료 필터
  • 개별 아이템의 수정 모드

이 네 가지를 각각 어디에 둘지 직접 정리해보면 데이터 흐름 감각이 빠르게 생깁니다. 이 과정을 하다 보면 컴포넌트 설계와 state 위치가 같이 움직인다는 점을 자연스럽게 이해하게 됩니다. 컴포넌트 분리가 먼저 헷갈린다면 React 컴포넌트 설계 가이드를 먼저 보는 것도 좋습니다.

FAQ

Q. props drilling은 무조건 나쁜가요?

아닙니다. 두세 단계 정도의 명확한 전달은 오히려 가장 단순한 해법일 때가 많습니다.

Q. state를 전역으로 올리면 더 편하지 않나요?

공유 범위가 넓을 때는 도움이 되지만, 필요 이상으로 전역화하면 오히려 변경 추적이 어려워질 수 있습니다.

Q. 서버 데이터도 모두 state인가요?

화면에 따라 다르지만, 단순 표시용 데이터라면 props나 데이터 패칭 훅의 반환값으로 다루는 편이 더 자연스러운 경우가 많습니다.

먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.