리액트에는 오랫동안 ‘Prop Drilling’이라는 이름의 고질병이 있었다.
‘Prop Drilling’이란, 최상위 컴포넌트가 가진 데이터를 아주 깊숙한 곳에 있는 자식 컴포넌트에게 전달하기 위해, 그 중간에 있는 모든 컴포넌트들이 릴레이 경주를 하듯 props
를 계속해서 넘겨주는 행위를 말한다.
중간에 있는 컴포넌트들은 정작 그 데이터가 필요하지도 않으면서, 오직 아래로 전달하기 위한 ‘배관’ 역할만 수행해야 했다. 이 과정은 코드를 불필요하게 복잡하게 만들고, 유지보수를 어렵게 했다.
물론, 리액트에는 이 문제를 해결하기 위한 ‘Context API’가 이미 존재했다.
Context는 컴포넌트 트리 전체에 데이터를 ‘방송’할 수 있는 일종의 전역 채널이었다.
// 1. Context 객체를 만든다.
const ThemeContext = React.createContext('light');
// 2. Provider로 데이터를 주입한다.
<ThemeContext.Provider value="dark">
<App />
</ThemeContext.Provider>
// 3. Consumer로 데이터를 꺼내 쓴다.
<ThemeContext.Consumer>
{theme => <Button theme={theme}>Click Me</Button>}
</ThemeContext.Consumer>
하지만 클래스 컴포넌트 시절의 Context API는 사용하기가 꽤 번거로웠다.
데이터를 사용하려면 항상 <ThemeContext.Consumer>
라는 컴포넌트로 UI를 감싸고, 그 안에 함수를 넣어 데이터를 받아와야 했다. 이것은 렌더 프롭 패턴과 유사한 구조였고, 여러 개의 Context를 함께 사용하면 ‘콜백의 피라미드’와 비슷한 문제를 일으켰다.
팀은 훅의 힘을 빌려, 이 Context API를 훨씬 더 쉽고 직관적으로 사용할 수 있는 방법을 모색했다.
해답은 놀랍도록 간단했다.
<ThemeContext.Consumer>
라는 컴포넌트를 사용하는 대신, useContext
라는 새로운 훅을 만드는 것이었다.
useContext
는 Context 객체를 인자로 받고, 그 Context가 현재 가지고 있는 value
를 즉시 반환했다.
새로운 방식을 적용한 코드는 다음과 같이 변했다.
// 1, 2번 단계는 동일.
// 3. useContext 훅으로 데이터를 꺼내 쓴다.
function Button() {
const theme = useContext(ThemeContext); // 이 한 줄로 끝!
return <button className={theme}>Click Me</button>;
}
변화는 혁신적이었다.
더 이상 JSX 안에 복잡한 <Consumer>
컴포넌트와 함수를 중첩할 필요가 없었다. 개발자는 그저 함수형 컴포넌트의 최상위에서 useContext
훅을 한번 호출하는 것만으로, 마치 지역 변수처럼 Context의 데이터에 손쉽게 접근할 수 있게 되었다.
이것은 useState
, useEffect
와는 성격이 조금 다른 훅이었다. 새로운 기능을 추가한 것이 아니라, 기존에 있던 강력하지만 불편했던 기능을, 훅이라는 새로운 패러다임에 맞춰 훨씬 더 사용하기 쉽게 ‘재포장’한 것이다.
useContext
의 등장은 커스텀 훅과 결합되었을 때 더욱 강력한 시너지를 냈다.
예를 들어, 인증(Authentication) 로직을 관리하는 커스텀 훅을 이렇게 만들 수 있었다.
function useAuth() {
return useContext(AuthContext);
}
// 그리고 어떤 컴포넌트에서든...
function UserProfile() {
const { user, logout } = useAuth(); // 사용자 정보와 로그아웃 함수를 손쉽게!
// ...
}
Prop Drilling의 시대는 서서히 저물고 있었다.
전역적으로 필요한 데이터(테마, 언어 설정, 사용자 정보 등)는 이제 Context와 useContext
훅의 조합을 통해, 컴포넌트 트리의 어느 깊은 곳에서든 깔끔하고 효율적으로 접근할 수 있게 되었다.
이제 리액트 팀의 무기고는 거의 완성되었다.
- 지역 상태 관리를 위한
useState
와useReducer
. - 부수 효과 관리를 위한
useEffect
. - 전역 상태 관리를 위한
useContext
.
이 핵심 훅들을 바탕으로, 개발자들은 커스텀 훅을 통해 무한한 가능성을 창조할 수 있었다.
기술적인 설계는 거의 마무리 단계에 접어들었다.
이제 남은 것은 이 새로운 발명품이 실제 세상의 복잡하고 거대한 코드베이스 위에서도 안정적으로 작동하는지 증명하는 일이었다.
그들은 자신들의 창조물을 데리고, 가장 혹독한 시험장으로 향할 준비를 했다. 바로 페이스북의 프로덕션 코드였다.