한국어

애플리케이션의 효율적인 상태 관리를 위해 React Context를 마스터하세요. Context 사용 시점, 효과적인 구현 방법 및 흔한 함정을 피하는 법을 배워보세요.

React Context: 종합 가이드

React Context는 컴포넌트 트리의 모든 레벨을 통해 명시적으로 props를 전달하지 않고도 컴포넌트 간에 데이터를 공유할 수 있게 해주는 강력한 기능입니다. 특정 하위 트리의 모든 컴포넌트가 특정 값에 접근할 수 있도록 하는 방법을 제공합니다. 이 가이드에서는 React Context를 효과적으로 사용하는 시기와 방법, 그리고 피해야 할 모범 사례와 일반적인 함정에 대해 알아봅니다.

문제 이해하기: Prop Drilling

복잡한 React 애플리케이션에서는 "prop drilling" 문제를 겪을 수 있습니다. 이는 부모 컴포넌트에서 깊게 중첩된 자식 컴포넌트로 데이터를 전달해야 할 때 발생합니다. 이를 위해, 중간 컴포넌트들이 해당 데이터가 필요하지 않더라도 모든 중간 컴포넌트를 통해 데이터를 전달해야 합니다. 이는 다음과 같은 문제를 야기할 수 있습니다:

다음의 간단한 예시를 살펴보겠습니다:


function App() {
  const user = { name: 'Alice', theme: 'dark' };
  return (
    <Layout user={user} />
  );
}

function Layout({ user }) {
  return (
    <Header user={user} />
  );
}

function Header({ user }) {
  return (
    <Navigation user={user} />
  );
}

function Navigation({ user }) {
  return (
    <Profile user={user} />
  );
}

function Profile({ user }) {
  return (
    <p>환영합니다, {user.name}님!
테마: {user.theme}</p>
  );
}

이 예시에서 user 객체는 실제로 Profile 컴포넌트에서만 사용됨에도 불구하고 여러 컴포넌트를 거쳐 전달됩니다. 이것이 바로 prop drilling의 전형적인 사례입니다.

React Context 소개

React Context는 props를 통해 명시적으로 데이터를 전달하지 않고도 하위 트리의 모든 컴포넌트가 데이터에 접근할 수 있도록 하여 prop drilling을 피할 수 있는 방법을 제공합니다. 이는 세 가지 주요 부분으로 구성됩니다:

React Context는 언제 사용해야 할까?

React Context는 React 컴포넌트 트리에서 "전역적"으로 간주되는 데이터를 공유하는 데 특히 유용합니다. 여기에는 다음이 포함될 수 있습니다:

중요 고려사항:

React Context 사용법: 실용적인 예제

prop drilling 예제로 돌아가서 React Context를 사용하여 해결해 보겠습니다.

1. Context 생성하기

먼저, React.createContext()를 사용하여 context를 생성합니다. 이 context는 사용자 데이터를 담게 됩니다.


// UserContext.js
import React from 'react';

const UserContext = React.createContext(null); // 기본값은 null 또는 초기 사용자 객체가 될 수 있습니다

export default UserContext;

2. Provider 생성하기

다음으로, 애플리케이션의 루트(또는 관련 하위 트리)를 UserContext.Provider로 감싸줍니다. user 객체를 value prop으로 Provider에 전달합니다.


// App.js
import React from 'react';
import UserContext from './UserContext';
import Layout from './Layout';

function App() {
  const user = { name: 'Alice', theme: 'dark' };
  return (
    <UserContext.Provider value={user}>
      <Layout />
    </UserContext.Provider>
  );
}

export default App;

3. Context 사용하기

이제 Profile 컴포넌트는 useContext hook을 사용하여 context에서 직접 user 데이터에 접근할 수 있습니다. 더 이상 prop drilling이 필요 없습니다!


// Profile.js
import React, { useContext } from 'react';
import UserContext from './UserContext';

function Profile() {
  const user = useContext(UserContext);

  return (
    <p>환영합니다, {user.name}님!
테마: {user.theme}</p>
  );
}

export default Profile;

중간 컴포넌트들(Layout, Header, Navigation)은 더 이상 user prop을 받을 필요가 없습니다.


// Layout.js, Header.js, Navigation.js
import React from 'react';

function Layout({ children }) {
  return (
    <div>
      <Header />
      <main>{children}</main>
    </div>
  );
}

function Header() {
  return (<Navigation />);
}

function Navigation() {
  return (<Profile />);
}

export default Layout;

고급 사용법 및 모범 사례

1. Context와 useReducer 결합하기

더 복잡한 상태 관리를 위해 React Context와 useReducer hook을 결합할 수 있습니다. 이를 통해 상태 업데이트를 더 예측 가능하고 유지보수하기 쉬운 방식으로 관리할 수 있습니다. context는 상태를 제공하고, reducer는 디스패치된 액션에 따라 상태 전환을 처리합니다.


// ThemeContext.js
import React, { createContext, useReducer } from 'react';

const ThemeContext = createContext();

const initialState = { theme: 'light' };

const themeReducer = (state, action) => {
  switch (action.type) {
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    default:
      return state;
  }
};

function ThemeProvider({ children }) {
  const [state, dispatch] = useReducer(themeReducer, initialState);

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

export { ThemeContext, ThemeProvider };



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

function ThemeToggle() {
  const { theme, dispatch } = useContext(ThemeContext);

  return (
    <button onClick={() => dispatch({ type: 'TOGGLE_THEME' })}>
      테마 전환 (현재: {theme})
    </button>
  );
}

export default ThemeToggle;



// App.js
import React from 'react';
import { ThemeProvider } from './ThemeContext';
import ThemeToggle from './ThemeToggle';

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

export default App;

2. 다중 Context 사용

관리해야 할 전역 데이터의 종류가 다른 경우, 애플리케이션에서 여러 context를 사용할 수 있습니다. 이는 관심사를 분리하고 코드 구성을 개선하는 데 도움이 됩니다. 예를 들어, 사용자 인증을 위한 UserContext와 애플리케이션 테마 관리를 위한 ThemeContext를 가질 수 있습니다.

3. 성능 최적화

앞서 언급했듯이, context 변경은 이를 사용하는 컴포넌트에서 리렌더링을 유발할 수 있습니다. 성능을 최적화하려면 다음을 고려하세요:

4. Context 접근을 위한 커스텀 Hook 사용

context 값에 접근하고 업데이트하는 로직을 캡슐화하기 위해 커스텀 hook을 만듭니다. 이는 코드 가독성과 유지보수성을 향상시킵니다. 예를 들어:


// useTheme.js
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

export default useTheme;



// MyComponent.js
import React from 'react';
import useTheme from './useTheme';

function MyComponent() {
  const { theme, dispatch } = useTheme();

  return (
    <div>
      현재 테마: {theme}
      <button onClick={() => dispatch({ type: 'TOGGLE_THEME' })}>
        테마 전환
      </button>
    </div>
  );
}

export default MyComponent;

피해야 할 일반적인 함정

React Context의 대안

React Context는 유용한 도구이지만 항상 최상의 해결책은 아닙니다. 다음 대안들을 고려해 보세요:

결론

React Context는 prop drilling 없이 컴포넌트 간에 데이터를 공유하기 위한 강력한 기능입니다. 언제 어떻게 효과적으로 사용해야 하는지를 이해하는 것은 유지보수 가능하고 성능이 뛰어난 React 애플리케이션을 구축하는 데 중요합니다. 이 가이드에 설명된 모범 사례를 따르고 일반적인 함정을 피함으로써, React Context를 활용하여 코드를 개선하고 더 나은 사용자 경험을 만들 수 있습니다. Context 사용 여부를 결정하기 전에 특정 요구 사항을 평가하고 대안을 고려하는 것을 잊지 마세요.