"그래서, state는 어떻게 바꿉니까?"
회의실의 모두가 기다렸다는 듯, 맷이 질문을 던졌다. 모든 논의가 결국 이 하나의 질문으로 귀결되었다. 컴포넌트의 내부 상태, 즉 this.state 객체를 변경하는 방법.
가장 단순하고 직관적인 방법은 직접 할당하는 것이었다.
this.state.isLiked = true;
하지만 조던 워크는 이 방법이 리액트의 근간을 흔드는 ‘금지된 주술’이라고 못 박았다.
“만약 우리가 this.state를 직접 수정하도록 허용한다면, 리액트는 상태가 언제 변했는지 절대로 알 수 없습니다.”
그의 설명은 명료했다. 리액트가 화면을 다시 그리는, 즉 ‘리렌더링(re-rendering)’을 촉발하는 유일한 신호는 ‘상태가 변경되었다’는 사실을 인지하는 것이다. 하지만 자바스크립트에서 객체의 속성값을 직접 바꾸는 행위는 아무런 흔적을 남기지 않는다. 리액트는 그저 this.state라는 객체의 메모리 주소만 바라보고 있을 뿐, 그 안의 내용물이 바뀌었는지는 감지할 방법이 없다.
결국 화면은 아무런 변화가 없을 것이다.
“우리는 리액트에게 상태가 바뀔 것이라는 사실을 ‘알려줘야’ 합니다. 우리가 직접 상태를 바꾸는 것이 아니라, 리액트에게 ‘이 값으로 상태를 바꿔주세요’라고 요청해야 하는 거죠.”
이 ‘요청’을 위한 공식적인 창구가 필요했다. 팀은 수많은 이름들을 놓고 고민했다. updateState, changeState, setState…
최종적으로 선택된 이름은 setState()였다.
가장 간결하고, 그 목적을 명확하게 드러내는 이름이었다.
this.setState({ isLiked: true });
이 한 줄의 코드는 단순한 함수 호출이 아니었다. 그것은 리액트의 마법을 깨우는 주문과도 같았다.
조던은 setState가 하는 일을 단계별로 설명했다.
-
상태 변경 요청 접수: 개발자가
this.setState({ isLiked: true })를 호출하면, 리액트는{ isLiked: true }라는 객체를 ‘상태 변경 요청서’로 접수한다. 중요한 것은, 이 시점에서this.state는 아직 바뀌지 않는다는 점이다.setState는 비동기적으로 동작할 수 있다. -
변경 사항 병합(Merge): 리액트는 전달받은 요청서 객체를 현재의
this.state객체와 병합하여 새로운 상태를 계산한다. 만약this.state가{ isLiked: false, count: 10 }이었고, 요청서가{ isLiked: true }였다면, 새로운 상태는{ isLiked: true, count: 10 }이 된다. 요청서에 포함되지 않은count값은 그대로 유지된다. -
리렌더링 계획(Scheduling a Re-render): 상태 변경이 완료되면, 리액트는 이 컴포넌트와 그 모든 자식 컴포넌트들을 ‘다시 그려야 할 대상’으로 표시한다. 그리고 가까운 미래의 어느 시점에
render메서드를 다시 호출할 것을 계획한다.
이 구조는 state 변경에 대한 모든 제어권을 리액트 라이브러리가 가져간다는 것을 의미했다. 개발자는 더 이상 상태를 직접 만질 수 없었다. 오직 setState라는 유일한 문을 통해서만 상태 변경을 ‘요청’할 수 있었다.
이 제약은 개발자에게 엄청난 선물을 주었다.
더 이상 상태를 바꾼 뒤에 view.render() 같은 코드를 수동으로 호출할 필요가 없었다. setState를 부르기만 하면, 리액트가 알아서, 가장 최적의 시점에, 화면을 다시 그려줄 것을 보장했다.
setState.
그것은 상태를 바꾸는 단 하나의 열쇠이자, 리액트의 반응형 시스템을 작동시키는 시동키였다. 이 함수가 호출되는 순간, 버추얼 DOM, Diffing 알고리즘, 그리고 재조정(Reconciliation)으로 이어지는 리액트의 모든 핵심 메커니즘이 비로소 잠에서 깨어나 움직이기 시작할 터였다.


