그런데, 컴포넌트 스스로 변하는 값은 어떻게 다루지?

542025년 10월 08일3

<LikeButton> 컴포넌트의 등장은 팀에 새로운 활력을 불어넣었지만, 동시에 더 깊은 질문을 수면 위로 끌어올렸다. props가 부모로부터 받은 ‘변경 불가능한’ 데이터라는 점에는 모두가 동의했다. 그렇다면 컴포넌트가 스스로의 행동에 따라 변경해야 하는 값들은 어떻게 다뤄야 하는가?

회의실에서 맷이 구체적인 사례를 들며 문제를 제기했다.
“댓글 입력창 <CommentBox>를 생각해봅시다. 사용자가 텍스트 에어리어에 글자를 입력하는 행위는 전적으로 컴포넌트 내부에서 일어나는 일입니다. 입력된 텍스트 값은 부모가 props로 내려준 게 아니죠. 이 값은 어디에 저장하고, 어떻게 관리해야 합니까?”

그의 질문은 리액트의 데이터 모델에 대한 근본적인 도전이었다. 만약 모든 데이터가 위에서 아래로만 흐른다면, 이런 ‘바닥에서 솟아나는’ 데이터는 설명할 방법이 없었다.

크리스도 거들었다. “사용자 검색창을 만든다고 가정해 보죠. 사용자가 입력한 검색어에 따라 자동완성 목록을 보여줘야 합니다. 이 ‘자동완성 목록이 현재 보이는 상태인가, 아닌가’ 하는 정보 역시 부모는 전혀 알 필요가 없는, 검색창 컴포넌트만의 내부적인 정보입니다. 이런 UI 상태들은 전부 어디에 둬야 하죠?”

이전 같았으면 개발자들은 이런 값들을 컴포넌트 객체의 일반 속성으로 저장했을 것이다.
this.commentText = '새로운 댓글';
this.isAutocompleteVisible = true;

하지만 리액트의 세계에서는 이 방식이 통하지 않았다. 왜냐하면 리액트는 propsstate가 변경되었을 때만 화면을 다시 그릴지 말지를 결정하기 때문이다. 개발자가 임의의 속성 this.commentText 값을 바꾼다고 해서, 리액트는 그 변화를 전혀 감지하지 못하고 render 함수를 다시 호출하지 않는다. 화면은 아무런 변화가 없을 것이다.

결국, 리액트가 ‘인식’하고 ‘반응’할 수 있는, 컴포넌트 내부만의 데이터 저장소가 필요했다.
props가 외부에서 오는 데이터라면, 이것은 내부에서 생성되고 관리되는 데이터여야 했다.

조던 워크는 이 문제에 대한 해답을 이미 준비해두고 있었다. 그가 <LikeButton> 예제에서 잠시 선보였던 바로 그 개념이었다.

“그것이 바로 우리가 state라고 부를 두 번째 데이터 소스입니다.”

조던은 화이트보드에 propsstate를 나란히 적고, 그 특징을 비교하기 시작했다.

props (Properties)

  • 부모 컴포넌트가 자식에게 전달한다.
  • 자식 컴포넌트 내부에서는 절대 변경할 수 없다 (읽기 전용).
  • 마치 함수의 인자(arguments)와 같다.

state (State)

  • 컴포넌트가 스스로 생성하고 관리하는 데이터이다.
  • 오직 컴포넌트 내부에서만 접근하고 변경할 수 있다. 외부에서는 이 state의 존재조차 알 수 없다.
  • 완벽하게 캡슐화되어 있다.
  • 마치 함수 내부에 선언된 지역 변수(local variables)와 같다.

props가 컴포넌트의 설정값이라면, state는 컴포넌트의 ‘기억’ 또는 ‘감정’과도 같습니다.” 조던이 설명했다. “<LikeButton>은 자신이 ‘좋아요가 눌렸는지’를 state에 기억하고, <CommentBox>는 사용자가 ‘무엇을 입력했는지’를 state에 기억하는 거죠.”

state의 등장은 리액트의 데이터 모델을 완성하는 마지막 퍼즐 조각이었다. 모든 데이터는 이제 둘 중 하나로 명확히 구분되었다. 부모에게서 왔는가, 아니면 스스로 가지고 있는가.

이 명확한 구분은 개발자가 데이터의 흐름과 소유권을 훨씬 쉽게 파악하게 만들어 줄 터였다.
하지만 state의 등장은 곧바로 또 다른 질문을 낳았다.

props는 부모가 새로운 값을 내려주면 자연스럽게 갱신된다.
그렇다면 state는, 컴포넌트가 스스로 가진 이 ‘기억’은, 대체 어떤 방식으로 ‘변경’해야 하는가? 그리고 그 변경을 리액트는 어떻게 감지하여 화면을 다시 그리는 마법을 부리는 것일까?

그 해답 속에 리액트의 가장 강력하고 핵심적인 메커니즘이 숨어 있었다.