Context API

손쉬운 컴포넌트 상태 공유

props, callback의 문제

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

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

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

// 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>
  }
}

컨텍스트(Context) 활용

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

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

// 컨텍스트 생성.
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>
)

컨텍스트 생성(create)

React.createContext API를 사용해 컨텍스트를 생성할 수 있습니다.

const Context = React.createContext();

컨텍스트 공급자(Provider)

생성된 컨텍스트 객체는 Provider 요소를 포함합니다. Provider는 중첩된 하위 컴포넌트(들)에게 value를 공급합니다.

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

컨텍스트 수요자(Consumer)

생성된 컨텍스트 객체의 Consumer 요소는 공급받은 value를 전달 받는 콜백 함수를 통해 컴포넌트를 렌더링합니다.

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

컨텍스트 타입(Type)

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

import Context from '../contexts/Context'

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

컨텍스트 기본값 설정

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

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

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

export default ThemeContext

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

import ThemeContext from '../contexts/Theme'

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

컴포넌트의 기본값은 Context.Provider 설정 없이도 value를 전달 할 수 있습니다. 단, "읽기전용" 입니다.

Last updated

Was this helpful?