본문 바로가기

React

[React] Context-API

React를 사용하다보면, 값을 전역적으로 관리하여야 할 때가 많다. 예를 들어, 테마나 언어 설정 등의 서비스 전반에서 공통적으로 사용되는 데이터 들이 그렇다.

부모 자식 간의 관계가 존재하는 컴포넌트 구조의 리액트 특성 상, 어떤 경우에는 이러한 값이 무의미하게 전달을 위해 거쳐가야하는 컴포넌트도 있을 수 있다. 이러한 상황을 props drilling이라고 하고, 리액트에서는 해결법으로 Context API와 같은 전역 상태 관리 방식을 제안한다.

 

props drilling?

전역 상태 관리가 생겨난 배경을 이해하기 위해 props drilling에 대해 간단히 짚어보자.

리액트에서는 컴포넌트를 생성하고 이를 이용하여 어플리케이션을 구조적으로 설계하기 때문에, 상태나 값, 함수 등을 자식 컴포넌트에 전달해야할 때가 많다. 대게 부모 컴포넌트에서 사용되는 상태 등이 자식에게 영향을 주거나, 받는데에 사용되지만, 자식 컴포넌트에서 사용되지 않고 보다 하위의 컴포넌트에서 사용되는 경우도 있다. 이 경우에는 자식 컴포넌트에서 사용되지 않음에도 더 하위의 컴포넌트에게 전달해주기 위해 props가 거쳐가는 현상이 발생하고, 이를 props drilling이라고 한다.

 

그림처럼 component 1에서 component 3으로 바로 전달이 불가능하기 때문에, props가 component 2를 거쳐가게 된다.

 

Context API

Context API는 리액트에서 컴포넌트 트리의 모든 곳에 데이터를 전달하는 것이 가능하게 해준다. 이를 이용함으로 1 → 2 → 3 이런식으로 props를 일일히 전달하지 않아도 된다.

크게 세가지로 구분해 볼 수 있다.

  • Context
  • Provider
  • Consumer

(Context API는 React 패키지의 기본 내장되어있다. 설치 필요 x)

 

Context

Context는 전역 상태 저장 공간이라고 이해하면 좋다. 정확히는 상태(값)을 참조할 수 있도록 해주는 통로 정도로 이해할 수 있겠다.

Context API의 사용을 위해선 이 Context를 먼저 생성해주어야한다.

import { createContext } from 'react';

export const ThemeContext = createContext('light');

 

이렇게 하면 "ThemeContext"라는 Context가 생성이 된 것이다.

 

Provider

Provider는 말 그대로 “제공자”의 역할을 한다.
미리 만들어진 통로인 Context를 통해 값을 하위 컴포넌트로 전달해줄 수 있도록 해주는 컴포넌트다.

function App() {
  return (
    <ThemeContext.Provider value="light">
      <ChildComponent />
    </ThemeContext.Provider>
  );
}

Provider를 퓨어하게 사용한다면 이처럼 사용할 수 있다. 이 provider의 역할은 'light'를 하위 컴포넌트에 전달하는 것이다.

그렇지만 대부분 단순 값이 아니라 상태 등을 저장하고자 할 것이다.

이를 위해서 커스텀 Provider를 만들어 사용한다.

import { useState } from 'react';
import { ThemeContext } from './ThemeContext';

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

이처럼 provider를 커스텀하여 컴포넌트로 만든다.

이제 이를 적용하고자 하는 위치(데이터가 필요한 영역)에 적용해준다.

import { ThemeProvider } from './ThemeContext';
import MainPage from './pages/MainPage';

function App() {
  return (
    <ThemeProvider>
      <MainPage />
    </ThemeProvider>
  );
}

 

Consumer

Consumer 역시 "소비자"의 역할을 한다.

Provider가 제공하는 데이터를 받아다 쓸 수 있도록 하는 컴포넌트이다.

...
return (
  <ThemeContext.Consumer>
    {(value) => (
      <div>나의 테마: {value.theme}</div>
    )}
  </ThemeContext.Consumer>
);

Consumer는 Context에서 제공받은 값을 함수의 형태로 받기 때문에, 사용하고자 하는 값이 매개변수로 전달된다.

하지만 언뜻 보기에도 사용이 불편하고, 단일 provider 환경과 달리 다중 provider 환경에서는 새로운 불편함이 발생한다.

// Provider부분

return (
  <ThemeProvider>
    <LanguageProvider>
      <App />
    </LanguageProvider>
  </ThemeProvider>
);


// Consumer 부분

return (
  <ThemeContext.Consumer>
    {(theme) => (
      <LangaugeContext.Consumer>
        {(language) => (
          <div>
            <p>테마: {theme}</p>
            <p>언어: {language}</p>
          </div>
        )}
      </LangaugeContext.Consumer>
    )}
  </ThemeContext.Consumer>
);

이처럼 콜백 함수를 중첩해서 사용해야하는 번거로움이 있다.

 

이를 간편히 하고자 useContext 훅을 사용한다.

import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function MyComponent() {
  const { theme } = useContext(ThemeContext);

  return <div>나의 테마: {theme}</div>;
}

 

 

전체 코드

// Context 생성 (ex. ThemeContext.js)
import { createContext } from 'react';

export const ThemeContext = createContext('light');

// 커스텀 Provider 컴포넌트 (ex. ThemeProvider.jsx)
import { useState } from 'react';
import { ThemeContext } from './ThemeContext';

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Provider 적용
import { ThemeProvider } from './ThemeContext';
import MainPage from './pages/MainPage';

function App() {
  return (
    <ThemeProvider>
      <MainPage />
    </ThemeProvider>
  );
}

// Consumer 적용 (useContext Hook 사용)
import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function MyComponent() {
  const { theme } = useContext(ThemeContext);

  return <div>나의 테마: {theme}</div>;
}

 

 

주의!

Context의 상태가 변경되면 Provider 하위의 모든 컴포넌트가 리랜더링 된다.

정말 필요한 컴포넌트만 분리해 참조하도록 설정하고, 가급적이면 Context를 그 의도에 맞게 여러 개로 분리해 생성하도록 하자.

 

 

 

 

'React' 카테고리의 다른 글

[React] Tailwind CSS  (0) 2025.06.04
[React] Recoil  (0) 2025.05.23
[React] React Router 페이지 접근 권한  (1) 2024.11.21
[React] useContext  (0) 2024.11.20
[React] filter의 무한루프와 useMemo  (0) 2024.11.14