React의 내장 기능인 Context API는 Prop Drilling 없이 전역 데이터를 공유하는 강력한 도구입니다. 하지만 잘못 사용하면 성능 저하의 주범이 되기 쉽습니다. 중급 개발자는 Context API의 한계를 정확히 파악하고, 이를 해결하는 **'분리 패턴(Separation Pattern)'**을 사용하여 성능과 편의성을 모두 잡아야 합니다.
이 섹션에서는 Context API의 단점을 최소화하고 효율성을 극대화하는 방법을 다룹니다.
1. 🔍 Context API의 작동 원리 및 문제점
1-1. Context API의 구조와 역할
- Provider: 데이터를 제공하는 컴포넌트로, value Props를 통해 상태나 함수를 하위 트리에 전달합니다.
- Consumer: useContext Hook을 사용하여 Provider가 제공하는 value를 가져다 쓰는 컴포넌트입니다.
Context API는 Redux나 Recoil 같은 외부 라이브러리 없이 테마, 사용자 인증 정보 등 전역적으로 필요한 데이터를 전달하는 데 매우 유용합니다.
1-2. Context의 치명적인 문제: 불필요한 리렌더링
Context API의 가장 큰 성능 이슈는 바로 전파 방식에 있습니다.
문제점: Context Provider의 value 객체가 변경되면, 해당 Context를 구독하고 있는 모든 하위 Consumer 컴포넌트는 value 내의 특정 값이 변하지 않았더라도 무조건 리렌더링됩니다.
만약 Context value에 자주 변하는 데이터(예: 알림 개수)와 거의 변하지 않는 데이터(예: 사용자 프로필)가 함께 있다면, 알림 개수 하나 때문에 수많은 컴포넌트가 불필요하게 리렌더링되는 비효율이 발생합니다.
2. 🛡️ Context 분리 전략 (Context Separation Pattern)
불필요한 리렌더링 문제를 해결하는 가장 확실한 방법은 '관심사에 따라 Context를 쪼개는' 것입니다.
2-1. 자주 변하는 상태와 불변 상태의 분리
- 분리: 자주 변하는 상태(예: 모달 상태, 알림 수)와 거의 변하지 않는 상태(예: 현재 언어, 사용자 정보)를 별도의 Context로 분리합니다.
- 효과: UserContext의 데이터가 변하더라도 ThemeContext를 구독하는 컴포넌트에는 영향을 주지 않습니다.
2-2. 상태와 액션(함수)의 분리
Context를 **'상태 Context'**와 **'디스패치(액션) Context'**로 분리하는 방법입니다.
- 상태 Context (StateContext): 읽기 전용 상태 값만 제공합니다. (자주 변함)
- 디스패치 Context (DispatchContext): 상태를 변경하는 함수만 제공합니다. (함수는 useCallback으로 감싸면 한 번 생성된 후 변하지 않음)
이렇게 분리하면, 상태를 변경하는 함수를 가져다 쓰는 컴포넌트가 상태 값 자체를 가져다 쓰는 컴포넌트보다 훨씬 적게 리렌더링됩니다.
3. 🎣 커스텀 훅을 이용한 Context 사용 패턴
Context API를 사용할 때 코드를 깔끔하게 유지하고 구독 로직을 단순화하는 모범 사례입니다.
3-1. Context Hook으로 감싸기
Context Provider와 Consumer를 직접 노출하지 않고, Custom Hook으로 감싸서 사용합니다.
// Before (복잡함)
const user = useContext(UserContext);
// After (간결함)
const user = useUser(); // useUser Hook 안에 Context 로직 캡슐화
// useUser Hook 구현 예시
const useUser = () => {
const context = useContext(UserContext);
// Context가 없을 때 에러 처리 로직 등을 추가할 수 있음
if (!context) throw new Error('useUser must be used within a UserProvider');
return context;
};
이 방식은 코드의 가독성을 높이고 Context 사용 시 필수적인 예외 처리 로직을 강제하는 효과가 있습니다.
3-2. useContext + useMemo를 이용한 최적화
Provider 컴포넌트의 value prop은 매 렌더링마다 새로운 객체 {}를 생성합니다. 이것이 Context를 구독하는 모든 하위 컴포넌트의 리렌더링을 유발합니다.
- 해결책: value prop에 전달되는 객체를 반드시 **useMemo**로 감싸서, 객체 내부의 핵심 상태가 변하지 않는 한 참조(Reference)가 유지되도록 해야 합니다.
🛠️ 2-4 실습 미션 (Challenge)
- 리렌더링 문제 재현: 하나의 Context (AppStateContext)에 자주 변하는 상태 (count)와 자주 변하지 않는 상태 (userInfo)를 모두 담으세요. count를 변경했을 때, userInfo만 사용하는 컴포넌트까지 리렌더링되는 것을 DevTools Profiler로 확인합니다.
- Context 분리 적용: CountContext와 UserContext 두 개로 분리하고, 각 Context를 구독하는 컴포넌트들이 각자의 상태가 변경될 때만 리렌더링되는 것을 확인하여 성능 개선을 검증하세요.
'개발 > React' 카테고리의 다른 글
| [중급반] Step 3-2. GraphQL 연동 심화 (REST API의 대안) (0) | 2025.12.15 |
|---|---|
| [중급반] Step 3-1. 서버 상태 관리 라이브러리 (React Query / SWR) (0) | 2025.12.15 |
| [중급반] Step 2-3. 컴포넌트 아키텍처 패턴 (결합도 낮추기) (0) | 2025.12.15 |
| [중급반] Step 2-2. 새로운 상태 관리 패러다임: Recoil/Zustand 비교 분석 (0) | 2025.12.15 |
| [중급반] Step 2-1. Redux Toolkit (RTK) 심화 마스터 (0) | 2025.12.15 |