context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.
context의 데이터는 전체에 공유될 수 있다. 그렇기에 전체에 영향을 미치는 데이터를 context로 관리하자. (예: 로그인한 유저 정보, 테마, 언어)
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// Toolbar 컴포넌트는 불필요한 테마 prop를 받아서
// ThemeButton에 전달해야 합니다.
// 앱 안의 모든 버튼이 테마를 알아야 한다면
// 이 정보를 일일이 넘기는 과정은 매우 곤혹스러울 수 있습니다.
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}
위 Toolbar 컴포넌트는 단순히 theme을 넘겨주기 위한 징검다리 역할만 하고 있다. Toolbar
컴포넌트에게 theme은 아무 의미없는 정보이고 코드의 품질이 떨어진다.
context를 이용한다면 위와 같은 문제를 해결할 수 있다.
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Provider를 이용해 하위 트리에 테마 값을 보내줍니다.
// 아무리 깊숙히 있어도, 모든 컴포넌트가 이 값을 읽을 수 있습니다.
// 아래 예시에서는 dark를 현재 선택된 테마 값으로 보내고 있습니다.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 이젠 중간에 있는 컴포넌트가 일일이 테마를 넘겨줄 필요가 없습니다.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 현재 선택된 테마 값을 읽기 위해 contextType을 지정합니다.
// React는 가장 가까이 있는 테마 Provider를 찾아 그 값을 사용할 것입니다.
// 이 예시에서 현재 선택된 테마는 dark입니다.
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />
}
}
context를 사용하면 컴포넌트를 재사용하기 어려워진다. 필요할 때만 쓰는게 좋다.
무엇보다 context보다 컴포넌트 합성이 더 간단한 해결책일 수 있다.
<Page user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
Avatar
컴포넌트에 사용할 데이터 user
와 avatarSize
를 위해 여러 중첩된 컴포넌트에서 props를 내려서 사용하고 있다. 이는 불편하다. 이러한 문제를 해결하기 위한 방법으로 context를 고려해볼 수 있지만, 그보다 Avatar
컴포넌트를 직접 내려주면 user
나 avatarSize
를 중간 컴포넌트들이 알 필요가 없게 해결할 수 있다. (컴포넌트 합성 방법)
이러한 제어의 역전(Inversion of Control)을 이용하면 넘겨줘야 하는 props의 수는 줄고 최상위 컴포넌트의 제어력은 더 강해지기 때문에 더 깔끔한 코드를 쓸 수 있는 경우가 많다. 하지만 이러한 역전이 항상 옳은 것은 아니다. 복잡한 로직을 상위로 옮기면 이 상위 컴포넌트들은 더 난해해지기 마련이고 하위 컴포넌트는 필요 이상으로 유연해져야 한다.
여러 자식을 넘길 수도 있다. 이러한 방식을 이용하면 부모와 자식간에 decoupling이 가능하고 render props를 이용하면 렌더링 되기 전부터 부모와 소통을 할 수 있게 된다.
function Page(props) {
const user = props.user;
const content = <Feed user={user} />;
const topBar = (
<NavigationBar>
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
</NavigationBar>
);
return (
<PageLayout
topBar={topBar}
content={content}
/>
);
}
이렇게 같은 데이터를 여러 데이터에 제공하는 방식이 context이다. 주로 locale, 테마, 데이터 캐시를 관리하는데 사용한다.