더 큰 그림: 동시성 모드(Concurrent Mode)

892025년 11월 12일4

훅이 리액트의 표준으로 자리 잡고, 생태계가 안정기에 접어들 무렵, 세바스티안 마크바게는 팀을 소집했다. 그의 얼굴에는 언제나처럼 평온함이 감돌았지만, 그 이면에는 오랫동안 품어왔던 거대한 비전을 공개하려는 미세한 흥분이 서려 있었다.

“우리는 지금까지 훅이 클래스의 문제점을 어떻게 해결했는지에 대해 이야기해 왔습니다.”

그가 조용히 입을 열었다.
“하지만 그것은 이야기의 절반에 불과합니다. 오늘, 저는 우리가 왜 훅을 ‘만들어야만’ 했는지, 그 진짜 이유에 대해 이야기하고 싶습니다.”

회의실의 모든 시선이 그에게로 향했다.
로직 재사용, 복잡성 관리… 이 모든 것보다 더 근본적인 이유가 있다는 말인가?

세바스티안은 스크린에 리액트의 전통적인 렌더링 모델을 보여주었다.
“지금까지 리액트의 렌더링은 ‘블로킹(blocking)’ 방식이었습니다. setState가 호출되면, 리액트는 전체 컴포넌트 트리를 다시 렌더링하는 작업을 시작하고, 그 작업이 끝날 때까지 다른 어떤 일도 할 수 없습니다. 렌더링은 중간에 멈추거나 중단될 수 없는, 하나의 거대한 동기적(synchronous) 트랜잭션이었죠.”

이 방식은 대부분의 경우 잘 작동했다.
하지만 CPU의 연산 능력이 떨어지는 저사양 기기에서, 복잡한 컴포넌트 트리를 렌더링하는 데 수백 밀리초가 걸린다면? 그 시간 동안 사용자가 버튼을 클릭하거나 텍스트를 입력해도, 브라우저는 아무런 반응도 하지 못하고 그대로 ‘얼어붙게’ 되었다. 사용자 경험은 최악으로 치달았다.

“이 문제를 해결하기 위해, 우리는 렌더링을 ‘중단 가능(interruptible)’하게 만들어야만 했습니다.”
세바스티안의 목소리에 힘이 실렸다.

“거대한 렌더링 작업을 잘게 쪼개서, 일부만 처리하고 잠시 멈춘 뒤, 사용자 입력 같은 더 중요한 작업을 먼저 처리하고, 다시 돌아와 나머지 렌더링을 이어가는 것. 우리는 이것을 ‘동시성 모드(Concurrent Mode)’라고 부릅니다.”

이것은 리액트의 렌더링 엔진을 완전히 새로 쓰는 것과 같은, 엄청난 도전이었다.
그리고 바로 이 지점에서, 클래스 컴포넌트는 치명적인 한계를 드러냈다.

“클래스 컴포넌트는 생명주기 메서드라는 예측 불가능한 부수 효과를 품고 있습니다. componentWillMount 같은 메서드는 렌더링 과정 중에 호출되죠. 만약 우리가 렌더링을 중간에 멈췄다가 나중에 다시 시작한다면, 이 생명주기 메서드들이 여러 번 호출되어 예기치 못한 버그를 일으킬 수 있습니다. 또한, 클래스 인스턴스 자체의 this 컨텍스트를 여러 렌더링 단계에 걸쳐 일관되게 유지하는 것도 매우 까다로운 문제입니다.”

순간, 회의실의 모두가 깨달았다.
아, 그래서였구나.

“하지만 함수형 컴포넌트와 훅은 다릅니다.”
세바스티안이 말을 이었다.
“함수는 호출될 때마다 새로운 상태와 props를 받아 처음부터 다시 실행됩니다. 이전 렌더링의 잔재가 남지 않죠. 상태는 컴포넌트 외부에서 리액트가 관리합니다. 덕분에 우리는 함수형 컴포넌트의 렌더링을 훨씬 더 안전하고 예측 가능하게 멈추고, 재개하고, 심지어는 폐기할 수도 있습니다.”

모든 퍼즐 조각이 맞춰졌다.

훅은 단지 클래스의 단점을 개선하기 위한 도구가 아니었다.
그것은 리액트의 렌더링 패러다임을 동기식에서 비동기식으로 전환하는, 거대한 ‘동시성 혁명’을 위한 필수적인 ‘초석’이었다.

로직 재사용의 편리함과 코드의 간결함은, 사실 이 더 큰 목표를 향해 나아가는 과정에서 얻어진 눈부신 ‘부산물’이었던 셈이다.

팀원들은 자신들이 참여했던 지난 몇 년간의 여정이 가진 진정한 의미를 비로소 이해했다. 그들은 단순히 더 나은 컴포넌트 작성법을 만든 것이 아니었다. 그들은 리액트가 더 빠르고, 더 유연하며, 더 나은 사용자 경험을 제공하는 차세대 UI 라이브러리로 도약할 수 있는 발판을 마련한 것이다.

훅의 이야기는 여기서 끝이 아니었다.
오히려, 훅은 이제 막 시작될 더 위대한 이야기의 서막을 열었을 뿐이었다.