회의실 한쪽 구석, 줄곧 침묵을 지키던 한 남자가 있었다. 그는 동료들의 열띤 토론을 그저 조용히 듣고만 있었다. 마치 폭풍의 눈처럼, 모든 혼란의 중심에서 고요함을 유지하고 있었다.
세바스티안 마크바게(Sebastian Markbåge).
리액트의 수석 설계자이자, 그 철학적 근간을 세운 인물. 그는 눈앞의 버그나 당장의 개발 효율성 너머, 라이브러리의 가장 근본적인 원칙과 미래를 바라보는 사람이었다. 그의 눈에는 동료들의 고뇌가 놀랍지 않다는 듯, 깊고 차분한 빛이 감돌았다.
마침내, HOC와 렌더 프롭의 한계에 대한 이야기가 끝났을 때, 그가 처음으로 입을 열었다. 그의 목소리는 낮고 조용했지만, 회의실의 모든 소음을 잠재우는 힘이 있었다.
“HOC와 렌더 프롭… 이것들은 증상일 뿐, 병의 근원이 아닙니다.”
모두의 시선이 그에게로 향했다.
“우리는 지금, 망치만 가지고 모든 것을 못으로 보려 하고 있습니다. 우리의 망치는 ‘컴포넌트’입니다.”
세바스티안은 천천히 말을 이어갔다.
“상태를 가진 로직을 재사용하고 싶을 때, 우리는 그 로직을 또 다른 컴포넌트 안에 담으려고 합니다. HOC는 컴포넌트로 컴포넌트를 감싸는 방식이고, 렌더 프롭은 컴포넌트가 컴포넌트에게 렌더링 책임을 위임하는 방식이죠. 두 방법 모두, 로직을 공유하기 위해 컴포넌트라는 UI 단위를 도구로 사용합니다.”
그는 잠시 말을 멈추고, 동료들의 얼굴을 하나하나 둘러보았다.
“하지만 ‘친구의 온라인 상태를 구독하는 것’이 정말 컴포넌트일까요? ‘마우스의 위치를 추적하는 것’이 시각적인 형태를 가져야만 할까요? 아닙니다. 그것들은 순수한 로직입니다. 우리가 UI를 구성하기 위해 만든 ‘컴포넌트’라는 틀에, 순수한 ‘로직’을 억지로 구겨 넣고 있기 때문에 이런 문제들이 발생하는 겁니다.”
그의 말은 정곡을 찔렀다.
팀원들은 지금까지 로직을 어떻게 더 잘 ‘포장’할지만 고민했다. 하지만 세바스티안은 ‘포장지’ 자체가 문제라고 말하고 있었다.
“리액트의 핵심 철학은 단순합니다. UI = f(state)
. UI는 상태의 함수다. 하지만 클래스 컴포넌트는 이 순수한 모델을 복잡하게 만듭니다. componentDidMount
나 componentDidUpdate
같은 생명주기 메서드들은 ‘상태(state)’가 아니라 ‘시간(time)’에 의존합니다. ‘컴포넌트가 마운트된 후에’ 무언가를 실행하라는 것은, 선언적인 코드가 아니라 명령적인 코드입니다.”
댄은 세바스티안의 말에 깊이 몰입했다. 그는 어렴풋이 느끼고 있던 문제의 본질이 명확한 언어로 정의되는 것을 들었다. 클래스 컴포넌트는 상태가 변하면 UI가 어떻게 보일지를 ‘선언’하는 동시에, 특정 시점에 무엇을 해야 할지를 ‘명령’하는 두 가지 역할을 위태롭게 수행하고 있었다.
세바스티안의 시선은 회의실 너머, 더 먼 곳을 향해 있었다.
“만약… 만약 로직을 컴포넌트에서 완전히 분리해서, 그냥 평범한 함수처럼 만들고 조합할 수 있다면 어떨까요? UI와 직접적인 관련이 없는 모든 것을요.”
그의 질문은 회의실에 새로운 파장을 일으켰다.
컴포넌트 없이, 오직 함수만으로 상태 로직을 다루고 재사용한다?
그것은 당시의 리액트 패러다임에서는 상상하기 힘든, 거의 금기에 가까운 아이디어였다. 하지만 리액트의 심장을 직접 설계한 그의 입에서 나온 말이기에, 그 무게는 결코 가볍지 않았다.
세바스티안은 해결책을 제시하지 않았다. 그는 그저, 팀이 바라보아야 할 새로운 북극성을 가리켰을 뿐이다.
그들은 이제 더 나은 패턴을 찾는 것이 아니었다.
리액트의 심장을 열고, 그 작동 방식을 재정의하는, 거대한 도전을 향해 첫걸음을 내딛고 있었다. 그리고 그 첫걸음의 방향을 제시할 또 한 명의 핵심 인물이, 세바숱안의 말을 가장 날카롭게 듣고 있었다.