this
와의 사소하지만 성가신 전투는 시작에 불과했다. 페이스북의 코드베이스가 비대해질수록, 댄과 리액트 팀은 훨씬 더 근본적인 문제와 마주하고 있었다.
새로운 기능 개발 요청이 떨어졌다.
‘특정 친구가 온라인 상태인지 실시간으로 확인하고, 프로필 사진 옆에 초록색 점을 표시하라.’
개발팀은 먼저 FriendProfile
이라는 컴포넌트를 만들었다. 이 컴포넌트는 친구의 아이디(ID)를 받아, 서버와 실시간으로 연결을 맺고, 온라인 상태가 바뀔 때마다 isOnline
이라는 내부 상태를 갱신했다. componentDidMount
에서 구독을 시작하고, componentWillUnmount
에서 구독을 해제하는, 전형적인 클래스 컴포넌트의 생명주기 로직이었다.
기능은 성공적으로 구현되었다.
문제는 바로 다음 주에 터졌다.
‘채팅방 헤더에 있는 친구 목록에서도 각 친구의 온라인 상태를 초록색 점으로 표시하라.’
개발팀은 순간 침묵했다.
이미 FriendProfile
컴포넌트에 친구의 온라인 상태를 확인하는 로직을 완벽하게 구현해놓았다. 하지만 그 로직은 FriendProfile
이라는 클래스의 생명주기 메서드 안에 단단히 결합되어 있었다. 다른 컴포넌트, 예를 들어 ChatHeader
가 그 로직을 가져다 쓸 방법이 마땅치 않았다.
한 주니어 개발자가 조심스럽게 입을 열었다.
“그… FriendProfile
에 구현된 코드를 복사해서, ChatHeader
에 붙여넣으면 되지 않을까요?”
그것은 가장 쉽고 빠른 방법이었다. 하지만 동시에 가장 위험한 방법이기도 했다.
만약 미래에 서버 API 주소가 바뀌거나, 구독 방식이 변경되면 어떻게 될까? 개발자는 FriendProfile
의 코드와 ChatHeader
의 코드를 모두 찾아 수정해야만 했다. 코드베이스에 똑같은 로직을 가진 ‘복제품’이 늘어날수록, 유지보수는 악몽이 되어갔다.
소프트웨어 공학의 오랜 격언이 댄의 머릿속을 맴돌았다.
‘반복하지 말라(Don't Repeat Yourself, DRY).’
그렇다면 복사-붙여넣기 외에 다른 방법은 무엇인가?
상태를 가지는 로직, 즉 ‘상태 로직(Stateful Logic)’을 어떻게 재사용할 수 있을까?
어떤 이들은 부모 컴포넌트에서 모든 상태 로직을 처리한 뒤, 그 결과를 자식들에게 props
로 내려주는 방식을 제안했다. 하지만 온라인 상태를 필요로 하는 컴포넌트가 수십 개라면? 그 컴포넌트들이 앱의 전혀 다른 부분에 흩어져 있다면? 모든 로직을 단 하나의 거대한 최상위 컴포넌트에 몰아넣는 것은 재앙의 시작일 뿐이었다.
이것이 바로 리액트가 마주한 두 번째 거대한 벽이었다.
리액트는 컴포넌트라는 UI 블록을 재사용하는 데에는 탁월했다. 하지만 그 블록 안에 생명을 불어넣는 ‘상태 관리 로직’을 재사용하는 우아한 방법은 제공하지 못했다.
댄은 다시 모니터 앞으로 돌아갔다.
화면에는 두 개의 파일이 나란히 떠 있었다. FriendProfile.js
와 ChatHeader.js
. 두 파일은 전혀 다른 UI를 그리고 있었지만, 그 안에는 친구의 온라인 상태를 확인하는 거의 동일한 코드 덩어리가 자리 잡고 있었다.
이것은 단순한 기술적 과제가 아니었다.
그것은 리액트의 철학에 대한 질문이었다.
컴포넌트는 UI를 재사용하기 위한 단위인가, 아니면 로직을 재사용하기 위한 단위인가? 만약 둘 다라면, 왜 로직의 재사용은 이토록 고통스러운가?
커뮤니티는 이미 이 문제에 대한 나름의 해답들을 내놓고 있었다. 하지만 그 해답들은 종종 문제를 해결하는 동시에, 또 다른 형태의 복잡성을 낳곤 했다. 댄과 리액트 팀은 그 해법들을 면밀히 검토하기 시작했다.
그들이 가장 먼저 마주한 것은 ‘고차 컴포넌트(Higher-Order Component)’라는 이름의, 아주 영리하지만 교활한 패턴이었다.