래퍼 지옥(Wrapper Hell).
이 단어는 페이스북 리액트 팀의 회의실에 무겁게 내려앉아 있었다. HOC는 로직 재사용이라는 문을 열었지만, 그 대가로 코드의 구조를 복잡한 미로로 만들었다. 디버깅은 고통스러웠고, 데이터의 흐름은 안갯속처럼 불투명했다.
“밖에서 감싸는 게 문제라면, 안에서 꺼내 쓰게 하면 어떨까요?”
침묵을 깬 것은 소피 알퍼트(Sophie Alpert)였다. 그녀는 언제나 개발자의 실질적인 고통에 집중하는, 날카로운 통찰력을 지닌 엔지니어였다.
“HOC의 근본적인 문제는 데이터가 암묵적으로 주입된다는 겁니다. props
가 어디서 오는지 알려면 컴포넌트 트리를 거슬러 올라가야 하죠. 이걸 투명하게 만들 순 없을까요?”
그녀의 아이디어는 발상의 전환이었다.
상태 로직을 가진 컴포넌트가 직접 UI를 그리는 대신, 자신의 상태를 인자로 넘겨주는 ‘함수’를 실행하게 만드는 것. 그리고 그 함수가 실제 UI 렌더링을 책임지는 방식이었다.
이해를 돕기 위해, 한 개발자가 화이트보드에 간단한 예시를 그리기 시작했다. 이번엔 마우스의 현재 위치를 추적하는 로직을 재사용하는 시나리오였다.
먼저, Mouse
라는 이름의 컴포넌트를 만든다. 이 컴포넌트의 유일한 책임은 마우스의 x, y 좌표를 추적하여 자신의 state
에 저장하는 것이다.
class Mouse extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
// 마우스 움직임을 감지해 state를 업데이트하는 로직
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
};
render() {
// 중요한 부분! 아무것도 직접 렌더링하지 않는다.
// 대신, this.props.render 라는 함수를 호출한다.
// 그리고 자신의 state를 그 함수에 인자로 넘겨준다.
return this.props.render(this.state);
}
}
Mouse
컴포넌트의 render
메서드는 어떤 HTML 태그도 반환하지 않았다. 그 대신, props
로 전달받은 render
라는 이름의 함수를 호출하고, 자신의 상태 { x: 0, y: 0 }
를 통째로 넘겨주었다. 이것이 바로 ‘렌더 프롭(Render Prop)’ 패턴의 심장이었다. Mouse
컴포넌트는 ‘무엇을’ 그릴지 결정하지 않았다. 오직 ‘언제’, ‘어떤 데이터와 함께’ 렌더링 함수를 실행할지만 결정했다.
이제 이 Mouse
컴포넌트를 사용하는 쪽의 코드는 다음과 같았다.
class App extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<p>The current mouse position is ({mouse.x}, {mouse.y})</p>
)}/>
</div>
);
}
}
마법 같은 일이 일어났다.
<Mouse>
컴포넌트는 render
라는 이름의 prop을 받았고, 그 prop의 값은 UI를 반환하는 함수였다. Mouse
컴포넌트는 자신의 최신 좌표값을 mouse
라는 이름으로 이 함수에 전달했고, 개발자는 그 데이터를 받아 원하는 UI를 마음껏 그릴 수 있었다.
회의실의 공기가 달라졌다. 팀원들은 이 패턴의 장점을 즉시 알아차렸다.
첫째, 래퍼 지옥이 사라졌다. 컴포넌트 트리는 <Mouse>
라는 단 하나의 컴포넌트만 보여줄 뿐, 불필요한 중첩이 없었다.
둘째, 데이터의 출처가 명확했다. mouse
라는 데이터는 바로 눈앞에 보이는 render={mouse => ...}
에서 온다는 사실이 너무나도 명백했다. 더 이상 데이터의 근원을 찾아 헤맬 필요가 없었다.
셋째, props
이름 충돌 문제가 원천적으로 해결되었다. 데이터를 받는 쪽에서 mouse
든, position
이든 원하는 변수명을 자유롭게 정할 수 있었다.
이것은 HOC보다 훨씬 정직하고 투명한 방식이었다. 로직의 공유와 렌더링의 제어를 완벽하게 분리해냈다.
댄은 희망을 보았다.
HOC의 망령에서 벗어날 진정한 탈출구가 드디어 모습을 드러낸 것 같았다. 커뮤니티 역시 렌더 프롭 패턴의 우아함에 열광하기 시작했다.
하지만 이 새로운 패턴 역시, 개발자들을 또 다른 형태의 미로로 이끌 것이라는 사실을, 그들은 아직 알지 못했다. 그 미로의 이름은 ‘피라미드’였다.