setState()가 리렌더링을 촉발한다는 사실을 이해하자, 팀원들은 리액트의 작동 방식에 대해 더 깊이 파고들기 시작했다. 특히 백본에 익숙했던 크리스는 한 가지 중요한 차이점을 발견하고 질문을 던졌다.
"잠시만요, 조던. 그럼 setState가 호출되면, 무조건 render 함수가 다시 실행된다는 뜻입니까?"
"네, 기본적으로는 그렇습니다." 조던이 대답했다. "리액트는 상태가 변했을 가능성이 있다고 판단하면, 일단 render를 다시 호출해서 새로운 버추얼 DOM을 얻어내는 것을 원칙으로 합니다."
크리스는 이 점이 이해가 가지 않았다.
"백본에서는 listenTo를 아주 세밀하게 설정할 수 있었습니다. 예를 들어, listenTo(model, 'change:username', this.renderUsername)처럼 모델의 username 속성이 바뀔 때만 특정 함수를 실행하게 할 수 있었죠. 이건 불필요한 렌더링을 막아주는 아주 중요한 최적화 기법이었습니다. 그런데 리액트는 state 객체 안의 어떤 작은 값이 바뀌든, 상관없이 무조건 전체 render를 다시 실행한다니… 이건 너무 낭비 아닌가요?"
그의 지적은 합리적이었다. state가 { a: 1, b: 2, c: 3 }과 같이 여러 속성을 가질 때, setState({ a: 100 })을 호출하면 a와 아무 상관없는 b와 c에 의존하는 UI 부분까지도 전부 다시 계산되는 것처럼 보였다.
조던은 이 ‘낭비’처럼 보이는 과정이 사실은 리액트가 선택한 의도적인 설계임을 설명했다.
"리액트는 '무엇이 바뀌었는지'를 추적하는 복잡함을 개발자에게서 완전히 없애는 것을 최우선 목표로 합니다. 만약 우리가 백본처럼 세밀한 리스닝 기능을 제공한다면, 개발자는 다시 '어떤 상태 변화가 어떤 UI에 영향을 미치는지'에 대한 인과 관계를 관리해야만 합니다. 우리는 그 복잡함 자체를 없애고 싶었습니다."
그는 리액트의 철학을 다시 한번 강조했다.
"개발자는 그저 '상태가 바뀌었어'라고 선언(setState)하고, '현재 상태라면 UI는 이런 모습이어야 해'라고 선언(render)하기만 하면 됩니다. 그 사이에서 일어나는 모든 일은 리액트의 책임입니다."
render 함수의 자동 재호출.
이것은 개발자가 신경 써야 할 부분을 극단적으로 줄여주는 대신, 그 부담을 리액트 라이브러리에게 넘기는 대담한 트레이드오프(Trade-off)였다.
"물론, render 함수가 다시 호출된다는 것이, 실제 DOM이 무조건 바뀐다는 뜻은 아닙니다." 조던이 덧붙였다. "render 함수를 실행해서 새로운 버추얼 DOM을 만드는 것은 자바스크립트 메모리 안에서 일어나는, 비교적 저렴한 작업입니다. 진짜 비싼 작업은 실제 DOM을 조작하는 거죠. 그리고 그 부분은 우리의 Diffing 알고리즘이 철저하게 막아줄 겁니다."
즉, 리액트는 ‘일단 다시 계산해보고, 달라진 게 없으면 아무것도 하지 않는’ 전략을 택한 것이다.
이것은 개발자의 정신적 비용(Mental Overhead)을 줄이는 것을 성능 최적화보다 더 높은 가치로 두겠다는, 리액트의 확고한 철학을 보여주는 증거였다.
개발자는 더 이상 자신의 컴포넌트가 언제 다시 렌더링될지 걱정하거나 수동으로 제어할 필요가 없었다. 그저 setState를 호출하면, 리액트가 모든 것을 알아서 해줄 것이라는 강력한 믿음을 가질 수 있게 된 것이다.
render 함수의 자동 재호출은 리액트의 선언적 패러다임을 지탱하는 가장 중요한 기둥 중 하나였다. 이 자동화된 메커니즘 덕분에, 개발자는 비로소 UI의 상태와 동기화를 맞추는 지루한 작업에서 벗어나, 애플리케이션의 본질적인 로직에만 집중할 수 있는 자유를 얻게 될 터였다.


