문제: Prop Drilling

582025년 10월 12일3

훅이 가져온 혁신적인 변화 속에서도, 리액트의 세계에는 오랫동안 해결되지 않은 채 개발자들을 괴롭히는 문제가 남아 있었다. 그것은 버그나 성능 저하처럼 치명적이지는 않았지만, 코드의 우아함을 갉아먹고 유지보수를 성가시게 만드는, 지루하고 반복적인 고통이었다.

그 문제의 이름은 ‘Prop Drilling’이었다.

어느 날, 댄은 페이스북의 UI를 구성하는 한 컴포넌트 트리를 살펴보고 있었다.
트리의 구조는 다음과 같았다.

<App>
  <Page>
    <Layout>
      <Header>
        <UserAvatar />
      </Header>
    </Layout>
  </Page>
</App>

최상위 컴포넌트인 <App>은 현재 로그인한 사용자의 정보, currentUser라는 객체를 상태로 가지고 있었다. 그리고 트리의 가장 깊은 곳에 있는 <UserAvatar> 컴포넌트는, 바로 그 currentUser 객체를 받아 사용자의 프로필 이미지를 화면에 그려야 했다.

데이터를 전달하는 유일한 방법은 props를 이용하는 것이었다.
<App> 컴포넌트는 자신이 가지고 있는 currentUser를 자식인 <Page>에게 prop으로 내려주었다.

// App.js
<Page currentUser={currentUser} />

하지만 <Page> 컴포넌트는 currentUser 데이터가 전혀 필요 없었다. 그럼에도 불구하고, 그 데이터를 자신의 자식인 <Layout>에게 전달하기 위해 어쩔 수 없이 prop으로 받아야만 했다.

// Page.js
<Layout currentUser={props.currentUser} />

이 릴레이는 계속되었다. <Layout><Header>에게, <Header>는 마침내 <UserAvatar>에게 currentUser를 전달했다.

// Layout.js
<Header currentUser={props.currentUser} />

// Header.js
<UserAvatar currentUser={props.currentUser} />

<Page>, <Layout>, <Header>.
이 세 개의 컴포넌트는 currentUser 데이터를 사용하지도 않으면서, 오직 데이터를 아래로 전달하기 위한 ‘배관공’ 역할만 수행하고 있었다. 만약 나중에 <UserAvatar>에 추가적인 데이터가 필요해진다면, 개발자는 이 세 개의 파일을 모두 열어 새로운 prop을 추가해야만 했다.

댄은 이 코드를 보며 나지막이 중얼거렸다.
“이건 로직이 아니야. 그저 데이터의 배관 작업일 뿐이지.”

Prop Drilling은 코드를 불필요하게 장황하게 만들었다.
중간에 있는 컴포넌트들은 자신과 관련 없는 데이터에 대해 알아야만 했고, 이는 컴포넌트의 재사용성을 심각하게 떨어뜨렸다. <Header> 컴포넌트를 다른 곳에서 재사용하고 싶어도, 항상 currentUser라는 불필요한 prop을 넘겨주어야만 했다.

이것은 훅이 등장하기 이전부터 존재했던, 리액트의 오래된 숙제였다.
물론, 이 문제를 해결하기 위한 Context API가 존재했지만, 클래스 시절의 <Consumer> 문법은 또 다른 종류의 복잡성을 낳았다.

하지만 이제, 리액트 팀의 손에는 useContext라는 새로운 무기가 들려 있었다.
그들은 이 지루하고 고통스러운 배관 작업을, 단 몇 줄의 코드로 우아하게 해결할 수 있음을 보여줄 준비가 되어 있었다.
Prop Drilling의 시대에 종말을 고할 시간이 다가오고 있었다.