본문 바로가기

JS Ecosystem

(React) 전역 상태 공유 : React Context API 심층 분석

(핵심 요약) React Context API 종합 평가

SPA(Single Page Application) 개발에서 props drilling을 피하고 전역 상태를 관리하는 것은 중요한 과제입니다. 다양한 상태 관리 라이브러리들이 존재하는 가운데, 저는 Redux Toolkit, Zustand, Recoil, Reactive Variable 등 여러 상태 관리 도구들을 사용해본 경험을 토대로 Context API가 특히 단순하고 직관적인 상태 공유가 필요한 경우 매우 효과적임을 확인했습니다.

 

Context API는 복잡한 store 기반 상태 관리에서 벗어나, 모듈화된 컴포넌트들 간에 상태를 쉽게 공유할 수 있는 점에서 큰 장점이 있습니다. 특히, 특정 기능을 수행하는 컴포넌트 그룹을 하나의 Context로 묶어 상태를 관리함으로써, 상태를 전역적으로 관리하지 않아도 된다는 점은 효율적인 설계 방법입니다.

 

그러나 Context API를 사용할 때 알아두어야 할 중요한 단점이 있습니다. Context에 묶인 상태가 변경되면, 그 Context를 구독하는 모든 하위 컴포넌트가 다시 렌더링된다는 것입니다. 이는 불필요한 렌더링을 초래할 수 있어 성능에 영향을 미칠 수 있습니다. 따라서 Context API는 상태 변화가 자주 일어나지 않거나, 렌더링 비용이 크지 않은 경우에 적합한 도구입니다.

 

이 문제를 해결하기 위해선, 상태를 세분화된 컨텍스트로 나누거나, useMemouseCallback과 같은 최적화 기법을 함께 사용하는 것이 좋습니다. 이렇게 하면 필요하지 않은 컴포넌트들이 불필요하게 렌더링되는 문제를 줄일 수 있습니다.

 

결론적으로, Context API는 복잡하지 않고 직관적인 상태 관리가 필요한 상황에서 매우 유용하지만, 상태 변화에 따른 성능 문제를 고려하여 적절한 사용 전략을 선택하는 것이 중요합니다.

React Context API: 전역 상태 관리의 핵심 도구

Single Page Application(SPA) 개발에서 전역 상태 관리는 필수적인 요소로, 다양한 컴포넌트 간의 데이터 공유와 상태 유지를 위해 효과적인 상태 관리 도구가 필요합니다. 여러 상태 관리 라이브러리와 도구가 존재하는 가운데, React Context API는 그 중에서도 강력하고 직관적인 선택지로 자리잡고 있습니다.

다양한 상태 관리 도구의 경험

Redux(특히 Redux Toolkit), Zustand, Recoil, Reactive Variable 등 다양한 상태 관리 라이브러리를 경험해본 결과, 현재는 React Context API를 가장 많이 활용하고 있습니다.

 

특히, store 기반의 복잡한 상태 관리 방식에서 벗어나 단순하고 직관적인 상태 공유가 필요한 경우, Context API가 더욱 효과적입니다. 이는 현재 재직 중인 회사에서도 채택하고 있는 상태 관리 도구이기도 합니다.

 

이 글에서는 React Context API의 핵심 개념과 구조를 명확히 정리하고, 실제 개발 환경에서 효과적으로 활용하기 위한 가이드라인과 유용한 팁들을 제시하겠습니다. 

 

React Context API를 다룰 내용은 다음 세 장으로 구성됩니다:

1장 정적 Context 사용하기

2장 동적 Context 사용하기

3장 Consumer 대신 Hook 사용하기

 

 

(토막지식) Context API의 핵심 개념
Context : 컴포넌트 트리 전체에 값을 전달하기 위한 컨테이너입니다. 컨텍스트 객체는 아래의 2개의 속성을 가집니다.
Provider : 하위 컴포넌트에게 값을 제공하는 역할을 합니다.
Consumer : 하위 컴포넌트가 컨텍스트 값을 구독할 수 있게 해줍니다.

 

(토막지식) Context API의 동작 방식
Context 생성 : React.createContext()를 사용하여 새로운 Context를 생성합니다.
Provider로 감싸기 : 최상위 컴포넌트 또는 필요한 영역을 Provider로 감싸고, value prop을 통해 Context 값을 제공합니다.
Consumer로 값 가져오기 : 하위 컴포넌트에서 useContext Hook을 사용하여 Context 값에 접근합니다.

 

 


**  1장 정적 Context 사용하기

1. 새로운 Context 만들기

아래는 Context를 생성하는 예시입니다. context를 생성하면서 themeColor의 초기값을 black으로 설정했습니다.

import { createContext } from "react";

interface ThemeContextType {
  themeColor: string;
}

const defaultTheme: ThemeContextType = { themeColor: "black" };
const ThemeContext = createContext<ThemeContextType>(defaultTheme);

export default ThemeContext;

 

 

위에서 생성한 ThemeContext는 컨텍스트 객체를 이루고 있는 Provider와 Consumer를 통해 활용할 수 있습니다.

아래는 Provider와 Consumer를 적용한 간단한 코드입니다. 각 단계에서 자세하게 설명하겠습니다.

 

1) Provider 속성

const App: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const themeValue = { themeColor: "red" };

  return <ThemeContext.Provider value={themeValue}>{children}</ThemeContext.Provider>;
};

export default App;

 

2)  Consumer 속성

const ColorBox = () => {
  return (
    <ThemeContext.Consumer>
     {value => (
       <div style={{ background: value.color}}></div>
      )
    </ThemeContext.Consumer>
}

 

2. Consumer 사용하기

앞서 context에서 themeColor의 초기값을 black으로 설정하였습니다. Context API가 없었다면 이 값을 하위 컴포넌트의  props로 전달해야 합니다. 그러나 Consumer는 하위 컴포넌트가 전역 상태를 구독할 수 있도록 합니다.

 

ThemeBox 컴포넌트를 최상위에서 렌더링하면 검정색 상자가 화면에 표시됩니다. 상자의 색상이 black인 이유는 전역 상태를 black으로 설정하였기 때문입니다.

import React from "react";
import ThemeContext from "../contexts/themeContext";

const ThemeBox: React.FC = () => {
  return (
    <ThemeContext.Consumer>
      {({ themeColor }) => (
        <div
          <h1>Current Theme: {themeColor}</h1>
        ></div>
      )}
    </ThemeContext.Consumer>
  );
};

export default ThemeBox;

 

 

(토막지식) Render Props

위의 코드에서 Consumer 사이에 중괄호를 열어서 그 안에 함수를 넣어 주었습니다. 이러한 패턴을 Function as a child 또는 Render Props라고 합니다.  Render Props는 함수 형태의 자식을 컴포넌트에 전달하여 그 함수를 호출하면서 동적으로 렌더링할 내용을 결정하는 방식입니다.

아래의 예제는 Render Props 패턴을 이해하는데 도움이 됩니다. 

const RenderComp: React.FC<{ children: (name: string) => React.ReactNode }> = ({ children }) => {
  return <div>안녕하세요, {children("홍길동")}!</div>;
};

 

위와 같은 RenderComp 컴포넌트가 있다면 사용할 때 아래와 같이 사용할 수 있습니다. RenderComp 컴포넌트의 자식안에 있는 { name => <strong>{name}</strong> } 는 함수입니다. 이 함수는 children props로 전달됩니다. 좀 더 세부적으로 이야기하면 이 함수는 name 이라는 매개변수를 받아 React의 strong 컴포넌트 안에 name을 렌더링하는 JSX 요소를 반환합니다. 따라서 이 함수는 문자열을 입력받고 JSX 요소를 출력으로 내보내는 형태로 작동합니다. 

<RenderComp>{ name => <strong>{name}</strong> }</RenderComp>

 

 

3. Provider 사용하기

createContext를 통하여 초기값이 black으로 설정되었습니다. Provider를 사용하면 Context의 value를 변경할 수 있습니다. 아래의 코드에서는 context의 Provider를 통해 themeColor에 red 값이 공유되었습니다.

import React from "react";
import ThemeBox from "./components/ThemeBox";
import ThemeContext from "./contexts/themeContext";

const App: React.FC = () => {
  return (
    <ThemeContext.Provider value={{ themeColor: "red" }}>
      <div>
        <ThemeBox />
      </div>
    </ThemeContext.Provider>
  );
};

export default App;

 

4. 결론 - 정적 React Context API

React Context API를 활용해 정적인 방식으로 전역 상태를 관리 방법을 살펴본 결과, 복잡한 상태 관리 도구 없이도 Context API를 통해 전역 상태를 손쉽게 하위 컴포넌트로 전달하고, 필요한 곳에서 구독할 수 있다는 점이 핵심이라는 것을 확인했습니다.

 

특히, Provider와 Consumer 패턴을 사용해 초기 값을 설정하고, 그 값을 필요로 하는 하위 컴포넌트에서 Render Props 패턴을 통해 쉽게 전역 상태에 접근하는 방법입니다. 이는 Context API가 SPA(Single Page Application)에서 전역 상태를 관리하는 데 매우 효과적인 도구임을 보여줍니다.

 

 


** 2장 동적 Context 사용하기

1. 동적 Context를 통한 상태 관리

앞서 정적 Context를 활용해 전역 상태를 공유하는 방법을 알아보았습니다. 이번 글에서는 전역적으로 공유된 상태를 실시간으로 변경할 수 있는 동적 Context 사용법을 살펴보겠습니다.

 

동적 Context를 통해 상태 업데이트가 필요한 상황에서, Redux와 같은 복잡한 상태 관리 도구를 도입하지 않고도, Context API만으로 상태 변경 기능을 충분히 구현할 수 있습니다.

 

이 방법은 value에 단순한 상태 값뿐만 아니라 상태를 변경할 수 있는 setState 함수도 함께 전달하여, 보다 유연하게 동적 상태 관리를 구현하는 방식입니다.

 

아래의 코드는 ThemeColorContext를 생성하고, 이를 통해 테마 색상 상태를 관리하는 예제입니다. ThemeColorContext는 테마 색상과 하위 색상을 관리하는 Context를 생성합니다. ThemeColorProvider 컴포넌트는 useState 훅을 사용하여 색상 상태를 관리하며, value 객체를 통해 상태와 업데이트 함수를 제공합니다.

import { createContext, useState } from "react";

const ThemeColorContext = createContext({
  state: { themeColor: "black", subThemeColor: "red" },
  actions: {
    setThemeColor: () => {},
    setSubThemeColor: () => {},
  },
});

const ThemeColorProvider = ({ children }) => {
  const [themeColor, setThemeColor] = useState("black");
  const [subThemeColor, setSubThemeColor] = useState("red");

  const value = {
    state: { themeColor, subThemeColor }, // 상태
    actions: { setThemeColor, setSubThemeColor }, // 업데이트 함수
  };

  return (
    <ThemeColorContext.Provider value={value}>{children}</ThemeColorContext.Provider>
  ); 
};

const { Consumer: ThemeColorConsumer } = ThemeColorContext;

export { ThemeColorProvider, ThemeColorConsumer };

export default ThemeColorContext;

 

2. 상태와 업데이트 함수의 분리

Provider의 value에는 상태는 state로, 업데이트 함수는 actions로 묶어서 전달하고 있습니다. 이렇게 state와 actions 객체를 따로 분리해 주면, 나중에 다른 컴포넌트에서 Context의 값을 사용할 때 편리합니다.

 

예를 들어, 아래와 같이 사용할 수 있습니다. ThemeColorDisplay와 ThemeColorChanger 컴포넌트는 ThemeColorContext를 사용하여 테마 색상 상태를 표시하고 변경하는 기능을 제공합니다. state와 actions를 분리함으로써, 컴포넌트에서 필요한 정보만을 쉽게 접근할 수 있습니다.

import React, { useContext } from "react";
import ThemeColorContext from "./ThemeColorContext";

const ThemeColorDisplay = () => {
  return (
    <ThemeColorContext.Consumer>
      {({ state }) => (
        <div style={{ color: state.themeColor }}>
          현재 테마 색상: {state.themeColor}, 서브 테마 색상: {state.subColor}
        </div>
      )}
    </ThemeColorContext.Consumer>
  );
};

export default ThemeColorDisplay;
const ThemeColorChanger = () => {
  return (
    <ThemeColorContext.Consumer>
      {({ actions }) => (
        <div>
          <button onClick={() => actions.setThemeColor("blue")}>파란색으로 변경</button>
          <button onClick={() => actions.setSubColor("green")}>초록색으로 변경</button>
        </div>
      )}
    </ThemeColorContext.Consumer>
  );
};

export default ThemeColorChanger;

 

3. 속성명 없이 상태와 함수를 전달하는 방법

전통적으로 state 와 actions 와 같은 속성명을 사용하여 상태와 함수를 구분하지만, 아래의 코드에서는 별도의 속성명 없이 상태 값과 상태 변경 함수를 직접 Context 에 전달하고 사용하고 있습니다.

 

1) Context 생성의 경우

const ThemeColorContext = createContext(
 { themeColor: "black",
   subColor: "red",
   setThemeColor: () => {},
   setSubColor: () => {},
  }
);

 

2) Context 구독의 경우

import React from "react";
import ThemeColorContext from "./ThemeColorContext";

const ThemeColorDisplay = () => {
  return (
    <ThemeColorContext.Consumer>
      {({ state }) => (
        <div style={{ color: state.themeColor }}>
          현재 테마 색상: {state.themeColor}, 서브 색상: {state.subColor}
        </div>
      )}
    </ThemeColorContext.Consumer>
  );
};

export default ThemeColorDisplay;

 

const ThemeColorChanger = () => {
  return (
    <ThemeColorContext.Consumer>
      {({ actions }) => (
        <div>
          <button onClick={() => actions.setThemeColor("blue")}>파란색으로 변경</button>
          <button onClick={() => actions.setSubColor("green")}>초록색으로 변경</button>
        </div>
      )}
    </ThemeColorContext.Consumer>
  );
};

export default ThemeColorChanger;

 

4. 결론 - 동적 React Context API

동적 React Context API를 사용하면 전역 상태를 효율적으로 관리하고, 실시간으로 상태를 업데이트할 수 있습니다. useState를 활용하여 전역 상태와 상태 변경 함수를 Context로 제공하고, 이 함수를 통해 전역 상태를 변경하면 해당 상태를 구독하고 있는 컴포넌트들도 자동으로 업데이트됩니다.

 

기존에는 state와 actions와 같은 속성명을 사용하여 상태와 함수들을 구분했지만, 속성명을 생략하고 상태 값과 함수들을 직접 Context로 전달함으로써, 보다 간결한 코드를 작성할 수 있습니다.

 

React Context API는 간단하게 전역 상태를 설정하고 공유할 수 있는 도구로, 특히 작은 규모의 프로젝트나 단순한 상태 관리가 필요한 경우에 매우 적합한 솔루션입니다.

 

추후 글에서는 Consumer 패턴 대신 useContext 훅을 사용하는 방법을 다룰 예정입니다. useContext를 활용하면 기존의 Render Props 패턴보다 훨씬 더 간결하고 가독성이 높은 코드를 작성할 수 있으며, 이를 통해 Context API를 보다 직관적이고 간편하게 사용할 수 있습니다.

 

 


** 3장 Consumer 대신 hook 사용하기

이번 글에서는 React Context API의 고급 사용법을 다루고, useContext 훅을 통해 더 간결하고 효율적으로 전역 상태를 관리하는 방법을 소개하겠습니다. 기존의 Consumer 패턴 대신, useContext 훅을 사용하여 보다 직관적인 방식으로 상태를 구독하고 업데이트할 수 있습니다.

 

useContext는 컴포넌트에서 Context를 더 쉽게 사용하게 해주는 React 훅으로, 상태를 가져오는 코드가 간결해지고, 가독성도 높아집니다. 이 글에서는 실전 예제와 함께 동적 Context와 useContext 훅을 사용하는 방법을 살펴보겠습니다.

 

1. useContext의 기본 개념

기존의 Consumer 패턴에서는 Render Props 방식으로 Context의 상태와 함수를 사용했습니다. 그러나 useContext 훅을 사용하면 코드가 간결해지며, 상태 값에 바로 접근할 수 있습니다.

 

1) 기존의 Consumer 패턴:

import React from "react";
import ThemeColorContext from "./ThemeColorContext";

const ThemeColorDisplay = () => {
  return (
    <ThemeColorContext.Consumer>
      {({ themeColor }) => (
        <div style={{ color: themeColor }}>현재 테마 색상: {themeColor}</div>
      )}
    </ThemeColorContext.Consumer>
  );
};
 

2) useContext 훅을 활용한 개선된 패턴:

import React, { useContext } from "react";
import ThemeColorContext from "./ThemeColorContext";

const ThemeColorDisplay = () => {
  const { themeColor } = useContext(ThemeColorContext); // useContext로 바로 값에 접근
  return <div style={{ color: themeColor }}>현재 테마 색상: {themeColor}</div>;
};

 

2. useContext 를 사용한 동적 상태 공유

이제 useContext Hook을 사용하여 상태와 상태 변경 함수를 쉽게 관리하는 방법을 실전 예제로 살펴보겠습니다. 아래 코드는 테마 색상을 동적으로 변경할 수 있는 예제입니다. 이 코드는 지금까지 다뤘던 정적 Context 사용법동적 Context 사용법을 결합하여 보여주는 예제입니다. 이를 통해 Context API가 어떻게 상태를 관리하고 변경하는지 보다 명확히 이해할 수 있을 것입니다.

 

1) Context 생성 및 Provider 설정

import React, { createContext, useState } from "react";

// 테마 색상 관련 Context 생성
const ThemeColorContext = createContext({
  themeColor: "black", // 초기 상태
  subColor: "red",
  setThemeColor: () => {},
  setSubColor: () => {},
});

export const ThemeColorProvider = ({ children }) => {
  const [themeColor, setThemeColor] = useState("black");
  const [subColor, setSubColor] = useState("red");

  // 상태와 상태 업데이트 함수를 value로 전달
  const value = {
    themeColor,
    subColor,
    setThemeColor,
    setSubColor,
  };

  return (
    <ThemeColorContext.Provider value={value}>
      {children}
    </ThemeColorContext.Provider>
  );
};

export default ThemeColorContext;

 

2) useContext를 활용한 컴포넌트 구성

 - 테마 색상 표시 컴포넌트 (ThemeColorDisplay)

import React, { useContext } from "react";
import ThemeColorContext from "./ThemeColorContext";

const ThemeColorDisplay = () => {
  const { themeColor, subColor } = useContext(ThemeColorContext); // 상태 가져오기

  return (
    <div>
      <h1 style={{ color: themeColor }}>현재 테마 색상: {themeColor}</h1>
      <p style={{ color: subColor }}>서브 색상: {subColor}</p>
    </div>
  );
};

export default ThemeColorDisplay;

 

 - 테마 색상 변경 컴포넌트 (ThemeColorChanger)

import React, { useContext } from "react";
import ThemeColorContext from "./ThemeColorContext";

const ThemeColorChanger = () => {
  const { setThemeColor, setSubColor } = useContext(ThemeColorContext);

  return (
    <div>
      <button onClick={() => setThemeColor("blue")}>파란색으로 변경</button>
      <button onClick={() => setSubColor("green")}>초록색으로 변경</button>
    </div>
  );
};

export default ThemeColorChanger;

 

3. 결론 - Consumer 대신 hook 사용하기

이번 3장에서는 useContext Hook을 사용하여 React Context API를 통해 전역 상태를 보다 효율적으로 관리하는 방법을 다루었습니다. 기존 Consumer 패턴을 활용한 방식과 비교해, useContext Hook을 도입하면서 코드는 더욱 간결해졌고, 가독성 또한 향상되었습니다. 특히, 전역 상태와 그 변경 함수를 함께 Context로 전달하여 동적인 상태 공유가 가능함을 확인했습니다.

 

정적 Context에서 시작하여 동적 Context로 확장해가는 과정은 상태 값을 변경하고 상태를 구독하는 컴포넌트 간의 관계를 명확하게 다루는 데 매우 중요한 기술입니다. 이를 통해 라이브러리 없이도 실시간으로 상태를 공유하고, 다양한 컴포넌트에서 이를 쉽게 활용할 수 있습니다. 이는 특히 작은 규모의 프로젝트나 단순한 (상태 관리가 아니고) 상태 공유가 필요한 프로젝트에서 유용합니다.

 

마지막으로 useContext Hook을 활용한 방법은 전역 상태 관리를 더 직관적이고 간단하게 만들어, 개발자들이 복잡한 상태 관리 도구에 의존하지 않고도 유연하게 상태를 관리할 수 있는 강력한 도구로 자리 잡고 있습니다. 이를 통해, React 기반 애플리케이션에서 상태 관리의 복잡성을 줄이면서도 재사용성과 유지보수성을 동시에 높일 수 있는 방법을 터득할 수 있었습니다.