프로그래밍/ReactJS

React Context API 완전 정복: Prop Drilling은 이제 그만!

shimdh 2025. 10. 15. 14:02
728x90

React 애플리케이션을 개발하다 보면, 컴포넌트 간 데이터 공유가 필수적인데요. 이때 'Prop Drilling'이라는 골칫덩이가 등장합니다. 상위 컴포넌트에서 하위 컴포넌트로 props를 여러 계층을 뚫고 전달해야 하는 이 문제는 코드가 복잡해지고 유지보수가 어려워지죠. 하지만 React의 Context API가 등장하면서 이 모든 고통이 사라집니다! Context API는 컴포넌트 트리의 모든 레벨에서 데이터를 쉽게 공유할 수 있는 강력한 도구입니다. 이 글에서는 Context API의 기본 개념부터 실전 사용법, 성능 최적화 팁까지 완벽히 다뤄보겠습니다. Prop Drilling과 영원히 작별하세요!

Context API란 무엇인가요?

React의 Context API는 props를 명시적으로 전달하지 않고 컴포넌트 간에 값을 공유하는 메커니즘입니다. 테마 설정, 사용자 인증 상태, 앱 전체 설정처럼 여러 컴포넌트가 자주 접근해야 하는 전역 데이터를 관리하는 데 딱 맞아요.

간단히 말해, Context는 '공급자(Provider)'와 '소비자(Consumer)'로 구성됩니다. Provider가 데이터를 제공하고, Consumer가 그 데이터를 소비하는 거죠. 이를 통해 컴포넌트는 공유 데이터에 접근하고, 데이터 변경 시 UI를 자동으로 업데이트할 수 있습니다. Redux 같은 복잡한 상태 관리 라이브러리가 필요 없을 때, Context API가 가벼운 대안이 됩니다.

728x90

Context를 사용해야 하는 이유

왜 굳이 Context API를 써야 할까요? 간단히 말해, 개발 효율성과 코드 가독성을 폭발적으로 높여줍니다. 주요 이점은 다음과 같아요:

  • Prop Drilling 방지: 컴포넌트가 깊게 중첩된 구조에서 props를 계층마다 전달하는 건 지옥입니다. Context API는 이 불필요한 전달을 없애 코드를 깔끔하게 만듭니다.
  • 중앙 집중식 상태 관리: 앱의 최상위에서 상태를 관리하고, 모든 자식 컴포넌트가 필요할 때 접근할 수 있게 해줍니다. 전역 상태 관리를 간단히 구현할 수 있어요.
  • 더 깔끔한 코드: 반복되는 props 전달 코드가 사라지면서 전체 코드베이스가 읽기 쉽고 유지보수하기 좋아집니다.

Prop Drilling 없이도 데이터를 자유롭게 공유할 수 있으니, React 개발의 생산성이 쑥쑥 올라갈 거예요!

Context 소비는 어떻게 작동하나요?

Context를 효과적으로 사용하려면 세 단계를 따르세요. 간단한 사용자 인증 예시를 통해 따라 해보죠.

1. 컨텍스트 생성

먼저 React.createContext()로 컨텍스트를 만듭니다. 이게 Provider와 Consumer의 기반이 됩니다.

import React from 'react';

const MyContext = React.createContext();

이 코드는 데이터를 공유할 '고유한 식별자'를 생성해요. 기본값을 설정할 수도 있지만, 보통 Provider에서 값을 제공하니 빈 객체로 시작합니다.

2. 컨텍스트 값 제공

앱의 최상위 컴포넌트에서 Provider로 자식 컴포넌트를 감싸고 value prop을 전달합니다.

const App = () => {
    const value = { userName: "John Doe", authenticated: true };

    return (
        <MyContext.Provider value={value}>
            <ChildComponent />
        </MyContext.Provider>
    );
};

Provider는 Context API의 심장입니다. 이 안에 감싸진 모든 자식 컴포넌트가 value에 접근할 수 있어요. 상태가 변경되면 자동으로 업데이트됩니다.

3. 컨텍스트 값 소비

자식 컴포넌트에서 값을 가져와 사용합니다. 함수형 컴포넌트라면 useContext 훅, 클래스 컴포넌트라면 Consumer를 씁니다.

예시 1: useContext 훅 사용 (함수형 컴포넌트 추천)

훅 덕분에 코드가 초간단해집니다!

import React, { useContext } from 'react';

const ChildComponent = () => {
    const { userName, authenticated } = useContext(MyContext);

    return (
        <div>
            <h1>환영합니다, {authenticated ? userName : "손님"}!</h1>
        </div>
    );
};

이 예시에서 ChildComponent는 인증 상태에 따라 다른 메시지를 보여줍니다. useContext는 함수형 컴포넌트의 표준 방법으로, 코드를 직관적으로 만듭니다. (React 16.8+에서 사용 가능)

예시 2: Consumer 컴포넌트 사용 (클래스 컴포넌트나 레거시 코드)

훅을 못 쓰는 경우에 유용합니다. 렌더 props 패턴을 활용해요.

import React from 'react';

const ChildComponentWithConsumer = () => (
    <MyContext.Consumer>
        {({ userName, authenticated }) => (
            <div>
                <h1>환영합니다, {authenticated ? userName : "손님"}!</h1>
            </div>
        )}
    </MyContext.Consumer>
);

Consumer는 함수를 자식으로 받아 현재 컨텍스트 값을 인수로 전달합니다. 기능은 동일하지만, 훅만큼 간결하지는 않아요.

전체 예제: 간단한 테마 토글 앱

이론만으로는 부족하죠? 실제로 테마(라이트/다크)를 전역으로 관리하는 예제를 보세요.

import React, { createContext, useContext, useState } from 'react';

// 1. 컨텍스트 생성
const ThemeContext = createContext();

const App = () => {
    const [theme, setTheme] = useState('light');

    // 2. Provider로 값 제공
    return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
            <Toolbar />
        </ThemeContext.Provider>
    );
};

const Toolbar = () => {
    const { theme, setTheme } = useContext(ThemeContext);
    return (
        <div>
            <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
                테마 변경: {theme}
            </button>
            <ThemedButton />
        </div>
    );
};

const ThemedButton = () => {
    const { theme } = useContext(ThemeContext);
    return (
        <button style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
            {theme} 테마 버튼
        </button>
    );
};

export default App;

이 예제에서 ToolbarThemedButton이 Provider 아래에 있으니, 테마 변경이 즉시 반영됩니다. Prop Drilling 없이 깔끔하죠?

주요 고려 사항

Context API는 강력하지만, 성능 트랩이 있어요. 잘 피하세요!

  • 재렌더링 문제: Provider의 value가 바뀔 때마다 모든 Consumer가 재렌더링됩니다. 큰 앱에서 조심하세요. 불필요한 재렌더링은 FPS를 떨어뜨릴 수 있어요.
  • 메모이제이션 필수: 객체/배열을 value로 넘길 때는 useMemo로 안정화하세요. 매 렌더링마다 새 객체가 생기면 재렌더링 지옥입니다.
import React, { useMemo, useState } from 'react';

const App = () => {
    const [userData] = useState({ name: "John Doe" });

    // 메모이제이션: userData가 변하지 않으면 재계산 안 함
    const providerValue = useMemo(() => ({ ...userData }), [userData]);

    return (
        <MyContext.Provider value={providerValue}>
            {/* 자식 컴포넌트들 */}
        </MyContext.Provider>
    );
};

useMemo는 계산 비용이 큰 값에 특히 유용합니다. React DevTools로 재렌더링을 프로파일링하며 최적화하세요.

추가 팁: 여러 Context가 필요하면 별도 파일로 분리하세요. 예: UserContext.js, ThemeContext.js.

결론

Context API를 마스터하면 React 앱의 공유 상태 관리가 한결 수월해집니다. useContext 훅으로 간단히 소비하고, Provider로 중앙 관리하며, 메모이제이션으로 성능을 지키세요. Prop Drilling의 복잡함 없이 더 깔끔하고 유지보수 쉬운 코드를 작성할 수 있어요. 지금 당장 프로젝트에 적용해보세요 – 당신의 React 개발이 업그레이드될 거예요!

728x90