> For the complete documentation index, see [llms.txt](https://yamoo9.gitbook.io/learning-react-app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yamoo9.gitbook.io/learning-react-app/learning-history/context-api.md).

# Context API

## props, callback의 문제

React의 데이터 흐름은 "단방향(One Way) 하향식"입니다. 위에서 아래로 `props`를 전달하고, 아래에서 `callback` 을 실행해 전달 받은 상태를 업데이트 합니다. 문제는 컴포넌트 트리가 복잡해지면 상태 관리가 매우 어려워집니다.

![](/files/-MVIqg_5LRr-RSPd9B8E)

뿐만 아니라, `props`를 아래 방향으로 한 단계씩 전달 ▸ 전달 해야 하기 때문에 불필요하게 중간에서 전달 받아 다시 아래 방향으로 전달하는 경우도 빈번하게 발생하게 됩니다.

![](/files/-MVIpuGM4XgdgZjzr3f0)

문제 상황을 이해하기 위해 다음의 예시 코드를 살펴봅니다. Z, A, B는 각각 App, MenuBar, SignIn 컴포넌트입니다.

{% tabs %}
{% tab title="props 문제 상황" %}

```jsx
// Z(App)가 인증 정보를 B(SignIn)에게 전달하기 위해
// A(MenuBar)에게 인증 정보를 전달 함.
class App extends React.Component {
  state = {
    authentification: true
  }
  render() {
    return (
      <div className="app">
        <MenuBar isAuth={this.state.authentification} />
        {/* ... */}
      </div>
    )
  }
}

// A(MenuBar)는 인증 정보가 필요 없지만,
// B(SignIn)에게 전달해야 함.
const MenuBar = props => (
  <div className="menubar">
    <SignIn isAuth={props.isAuth} />
    {/* ... */}
  </div>
)

// A(MenuBar)를 거쳐 Z(App)가 전달한 
// 인증 정보를 힘겹게 받은 B(SignIn)
const SignIn = props => {
  const signed = props.isAuth
  if (signed) {
    return <div className="signed">로그인 사용자</div>
  } else {
    return <div className="un-signed">로그인 되지 않음</div>
  }
}
```

{% endtab %}
{% endtabs %}

## 컨텍스트(Context) 활용 <a href="#using-context" id="using-context"></a>

React는 복잡한 컴포넌트 트리의 "상태 공유 문제를 해결하기 위한 방법"으로 [컨텍스트(Context)](https://ko.reactjs.org/docs/context.html) API를 제공합니다. Context API를 사용하면 상위 컴포넌트에서 하위에 종속된 모든 컴포넌트에 데이터를 공급(Provider)한 후, 특정 위치의 컴포넌트에서 바로 수요(Consumer) 하도록 설정할 수 있습니다.

![](/files/-MVIs-hDeOh1Rg2aPmv4)

Context를 통한 데이터 "공급", "수요"의 의미가 아직은 모호하게 느껴질 수 있습니다. 아래 예시 코드를 살펴보면 그 모호함이 다소 해소될 수 있을 것입니다.

{% tabs %}
{% tab title="Context 활용 예" %}

```jsx
// 컨텍스트 생성.
const AuthContext = React.createContext();

// -------------------------------------------------------------

class App extends React.Component {
  state = {
    authentification: true
  }
  render() {
    // AuthContext.Provider 요소를 사용해 
    // 인증 정보를 값(value)으로 공급
    const value = { ...this.state }
    return (
      <div className="app">
        <AuthContext.Provider value={value}>
          <MenuBar />
          {/* ... */}
        </AuthContext.Provider>
      </div>
    )
  }
}

const MenuBar = () => (
  <div className="menubar">
    <SignIn isAuth={props.isAuth} />
    {/* ... */}
  </div>
)

// AuthContext.Consumer를 통해 전달 받은 
// 값(value)을 사용해 렌더링.
const SignIn = () => (
  <AuthContext.Consumer>
    {
      (context) => context ? 
        <div className="signed">로그인 사용자</div> : 
        <div className="un-signed">로그인 되지 않음</div>
    }
  </AuthContext.Consumer>
)
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
Context의 주된 사용 목적은 복잡하게 "중첩 된" 하위 컴포넌트(들)에 데이터를 공유하는 것입니다. Context를 사용하면 컴포넌트 재사용이 어려우므로 꼭 필요한 경우에만 사용합니다. ([참고](https://ko.reactjs.org/docs/context.html#consuming-multiple-contexts))
{% endhint %}

## 컨텍스트 생성(create)

React.[createContext](https://ko.reactjs.org/docs/context.html#reactcreatecontext) API를 사용해 컨텍스트를 생성할 수 있습니다.&#x20;

```jsx
const Context = React.createContext();
```

## 컨텍스트 공급자(Provider)

생성된 컨텍스트 객체는 [Provider](https://ko.reactjs.org/docs/context.html#contextprovider) 요소를 포함합니다. Provider는 중첩된 하위 컴포넌트(들)에게 `value`를 공급합니다.

```jsx
<Context.Provider value={value}>
  {/* 중첩된 컴포넌트 */}
  <NestedComponent />
</Context.Provider>
```

## 컨텍스트 수요자(Consumer)

생성된 컨텍스트 객체의 [Consumer](https://ko.reactjs.org/docs/context.html#contextconsumer) 요소는 공급받은 `value`를 전달 받는 콜백 함수를 통해 컴포넌트를 렌더링합니다.

```jsx
<Context.Consumer>
  {
    (context) => {
      // React 요소 반환
      return <></> 
    }
  }
</Context.Consumer>
```

## 컨텍스트 타입(Type) <a href="#context-type" id="context-type"></a>

클래스 컴포넌트의 `contextType` 스태틱(Static) 속성에 컨텍스트 객체를 설정하면 `this.context`를 통해 컨텍스트 객체의 값을 읽고 사용할 수 있습니다.

```jsx
import Context from '../contexts/Context'

class ClassComponent extends React.Component {
  static contextType = AuthContext
  render() {
    // 컨텍스트의 value 읽기
    console.log(this.context) 
    return <></>
  }
}
```

{% hint style="warning" %}
이 방법은 클래스 컴포넌트에서만 사용 가능합니다. 함수 컴포넌트는 다른 방법을 사용합니다.
{% endhint %}

{% hint style="warning" %}
컨텍스트 타입을 사용하면 단 하나의 컨텍스트 객체만 사용 가능합니다. 하나 이상의 컨텍스트 객체를 사용하려면 앞서 살펴 본 Context.Consumer를 중첩해 사용해야 합니다. ([참고](https://ko.reactjs.org/docs/context.html#consuming-multiple-contexts))
{% endhint %}

## 컨텍스트 기본값 설정 <a href="#context-initial-value" id="context-initial-value"></a>

생성 된 컨텍스트 객체에 기본값을 설정할 수 있습니다. 예를 들어 라이트(light), 다크(dark) 테마의 컬러 정보를 컨텍스트에서 공급하고자 할 수 있습니다. 그런 경우 다음과 같이 테마 정보 객체를 컨텍스트 생성 과정에 전달합니다.

{% tabs %}
{% tab title="contexts/Theme.js" %}

```jsx
// 테마 정보 객체
const themes = {
  light: {
    fg: '#1c2739',
    bg: '#fcfcfc'
  },
  dark: {
    fg: '#fcfcfc',
    bg: '#1c2739'
  }
}

// 테마 컨텍스트 생성
const ThemeContext = React.createContext(
  themes.dark // 기본값 설정
)

export default ThemeContext
```

{% endtab %}
{% endtabs %}

아래 코드는 컨텍스트 타입을 통해 컨텍스트의 기본 값을 Button 컴포넌트의 `theme` 속성으로 전달합니다.

{% tabs %}
{% tab title="components/ThemedButton.js" %}

```jsx
import ThemeContext from '../contexts/Theme'

class ThemedButton extends React.Component {
  static contextType = ThemeContext
  render() {
    // 컨텍스트 value 전달
    return <Button theme={this.context} />
  }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
컴포넌트의 기본값은 Context.Provider 설정 없이도 value를 전달 할 수 있습니다. 단, "읽기전용" 입니다.&#x20;
{% endhint %}
