context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.

언제 context를 사용할까?

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를 사용하면 컴포넌트를 재사용하기 어려워진다. 필요할 때만 쓰는게 좋다.

무엇보다 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 컴포넌트에 사용할 데이터 useravatarSize를 위해 여러 중첩된 컴포넌트에서 props를 내려서 사용하고 있다. 이는 불편하다. 이러한 문제를 해결하기 위한 방법으로 context를 고려해볼 수 있지만, 그보다 Avatar컴포넌트를 직접 내려주면 useravatarSize를 중간 컴포넌트들이 알 필요가 없게 해결할 수 있다. (컴포넌트 합성 방법)

이러한 제어의 역전(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, 테마, 데이터 캐시를 관리하는데 사용한다.