React Context

React 프로젝트에서 사용자 정보와 같은 전역적으로 필요한 데이터를 관리해야 할 때는 어떻게 해야 할까? 일반적인 React 애플리케이션에서 데이터는 부모에서 자식으로 props를 통해 전달되지만 애플리케이션 안의 여러 컴포넌트들에 전달해줘야 하는 props의 경우 컴포넌트 여기저기에 전달하기에는 이 과정이 번거로울 수 있고 컴포넌트가 많아질 수록 더욱 복잡해지고 유지 보수하기가 힘들어질 수 있다.

이런 경우 context API를 이용하면 컴포넌트들에 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다. context는 React 16.3 버전 이후 부터 많이 개선되어 전보다 사용하기가 훨씬 쉬워졌다.

API

React.createContext

const MyContext = React.createContext(defaultValue);
  • createContext 함수를 통해 Context를 만들 수 있다.
  • Context 객체를 구독하고 있는 컴포넌트를 렌더링할 때 React는 트리 상위에서 가장 가까이 있는 Provider로부터 현재 값을 얻는다.
  • 적절한 Provider를 찾지 못했을 경우 defaultValue 값을 사용한다.

context 만들기 샘플코드 (color.js)

import { createContext } from 'react';

const ColorContext = createContext({ color: 'black' });

export default ColorContext;

Context.Consumer

<MyContext.Consumer>
  {value => /* context 값을 이용한 렌더링 */}
</MyContext.Consumer>
  • context 변화를 구독하는 React 컴포넌트
  • 함수 컴포넌트 안에서 context를 읽기 위해서 쓸 수 있다.
  • Context.Consumer의 children은 함수여야 한다.
  • 이 함수는 context의 현재 값을 받고 React 노드를 반환한다.
  • 이 함수가 받는 value 값은 해당 context의 Provider 중 상위 트리에서 가장 가까운 Provider의 value prop과 동일하다.
  • 상위에 Provider가 없다면 value 매개변수 값은 createContext()에 보냈던 defaultValue 값이다.

Consumer 사용하기 샘플코드(ColorBox.js)

import React from 'react';
import ColorContext from '../contexts/color';

const ColorBox = () => {
  // Render Props
  // ColorContext 안에 있는 Consumer를 통해 context의 색상을 조회한다.
  return (
    <ColorContext.Consumer>
      {value => (
        <div
          style={{ width: '64px', height: '64px', background: value.color }}
        />
      )}
    </ColorContext.Consumer>
  );
};

export default ColorBox;

* Consumer 대신 useContext 사용하여 값 받아오기

import React, { useContext } from 'react';
import ColorContext from '../contexts/color';

const ColorBox = () => {
  const { state } = useContext(ColorContext);
  return (
    <>
      <div
        style={{
          width: '64px',
          height: '64px',
          background: state.color
        }}
      />
      <div
        style={{
          width: '32px',
          height: '32px',
          background: state.subcolor
        }}
      />
    </>
  );
};

export default ColorBox;

Context.Provider

<MyContext.Provider value={/* 어떤 값 */}>
  • Provider를 사용하면 Context의 value 값을 변경할 수 있다.
  • context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다.
  • Provider는 value prop를 받아서 이 값을 하위에 있는 컴포넌트에게 전달한다.(값을 전달받을 수 있는 컴포넌트 수에 제한은 없다.)
  • Provider 하위에 또 다른 Provider를 배치하는 것도 가능하며, 이 경우 하위 Provider의 값이 우선시된다.
  • Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value prop가 바뀔 때마다 다시 렌더링된다.
  • createContext 함수의 defaultValue는 Provider를 사용하지 않았을 때만 사용된다. 만약 Provider를 사용했는데 value를 명시하지 않았다면 에러가 발생한다.

Provider 사용하기 샘플코드 (App.js)

import React from 'react';
import ColorBox from './components/ColorBox';
import ColorContext from './contexts/color';

function App() {
  return (
    <ColorContext.Provider value={{ color: 'blue' }}>
      <div>
        <ColorBox />
      </div>
    </ColorContext.Provider>
  );
}

export default App;

값이 변하는 context

theme 값에 따라 변하는 button style

theme-context.js

import { createContext } from 'react';

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
    color: 'black'
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
    color: 'white'
  }
};

export const ThemeContext = createContext(themes.dark);

themed-button.js

import React, { useContext } from 'react';
import { ThemeContext } from '../contexts/theme-context';

const ThemedButton = props => {
  const { background, color } = useContext(ThemeContext);
  return <button {...props} style={{ background: background, color: color }} />;
};

export default ThemedButton;

App.js

import React, { useState } from 'react';
import { ThemeContext, themes } from './contexts/theme-context';
import ThemedButton from './components/themed-button';

function App() {
  const [theme, setTheme] = useState(themes.dark);

  const toggleTheme = () => {
    setTheme(theme === themes.light ? themes.dark : themes.light);
  };

  return (
    <ThemeContext.Provider value={theme}>
      <ThemedButton onClick={toggleTheme}>Change Theme</ThemedButton>
    </ThemeContext.Provider>
  );
}

export default App;

reference: