Redux 스타일 셀렉터로 React Context 재렌더링 방지
Source: Dev.to
문제
React Context를 사용할 때, 해당 컨텍스트를 구독하는 모든 컴포넌트는 그 컨텍스트 안의 어떤 값이든 변경될 때마다 다시 렌더링됩니다.
import { createContext, useState } from 'react';
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
return (
{children}
);
}
import { useContext } from 'react';
function UserProfile() {
const { user } = useContext(AppContext);
return Hello, {user?.name}!;
}
문제: UserProfile은 user만 필요함에도 불구하고 theme이나 notifications가 바뀔 때마다 다시 렌더링됩니다.
해결책
use-context-hook은 Redux‑style 셀렉터를 사용해 필요한 값만 구독하도록 해줍니다.
import { createContextHook, useContextHook } from 'use-context-hook';
import { useState } from 'react';
// 컨텍스트 생성
const AppContext = createContextHook();
// Provider는 그대로 유지
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
return (
{children}
);
}
// 컴포넌트는 `user`가 바뀔 때만 다시 렌더링
function UserProfile() {
const user = useContextHook(AppContext, 'user');
return Hello, {user?.name}!;
}
이제 UserProfile은 오직 user가 바뀔 때만 업데이트됩니다.
네 가지 유연한 셀렉터 패턴
1. 문자열 셀렉터 (단일 값)
const user = useContextHook(AppContext, 'user');
2. 배열 셀렉터 (다중 값)
const { user, theme } = useContextHook(AppContext, ['user', 'theme']);
3. 객체 셀렉터 (명시적 선택)
const { user, theme } = useContextHook(AppContext, {
user: 1,
theme: 1,
});
4. 함수 셀렉터 (Redux‑Style)
// 특정 필드만 선택
const { user, theme } = useContextHook(AppContext, (state) => ({
user: state.user,
theme: state.theme,
}));
// 혹은 값을 파생시킴
const userName = useContextHook(AppContext, (state) => state.user?.name);
실제 성능 영향
- 전: 사용자 인터랙션당 50번 이상의 불필요한 재렌더링
- 후: 실제로 업데이트가 필요한 2~3개의 컴포넌트만 렌더링
예시: 사용자가 입력 필드에 타이핑할 때 폼의 재렌더링 횟수가 47에서 3으로 감소했습니다.
작동 원리 (기술적인 부분)
useContextHook은 제공된 셀렉터를 기반으로 컴포넌트에 리스너를 등록합니다.- 컨텍스트 값이 변하면 라이브러리는 이전 값과 새로운 값 모두에 셀렉터를 실행합니다.
- 셀렉터 결과를 깊은 비교(deep comparison)합니다.
- 선택된 값이 변경된 경우에만 재렌더링을 트리거합니다.
이 방식은 중첩된 객체가 있더라도 변경되지 않은 데이터에 대한 재렌더링을 방지합니다.
TypeScript 지원
interface AppContextType {
user: User | null;
theme: 'light' | 'dark';
notifications: Notification[];
}
const AppContext = createContextHook();
// User | null 로 추론됨
const user = useContextHook(AppContext, 'user');
// { user: User | null; theme: 'light' | 'dark' } 로 추론됨
const { user, theme } = useContextHook(AppContext, ['user', 'theme']);
완전한 타입 추론과 안전성을 기본 제공합니다.
언제 사용해야 할까?
사용 사례
- 값이 많은 대형 컨텍스트 객체
- 컴포넌트가 컨텍스트의 특정 부분만 필요로 할 때
- 기본 Context API 사용 시 성능 문제가 발생할 때
- Redux 없이 Redux‑like 셀렉터 패턴을 원할 때
사용하지 말아야 할 경우
- 컨텍스트에 1~2개의 값만 있을 때
- 모든 컴포넌트가 전체 컨텍스트를 필요로 할 때
- 이미 Redux, Zustand 등 선택적 구독을 지원하는 상태 관리 라이브러리를 사용 중일 때
설치
npm install use-context-hook
# or
yarn add use-context-hook
# or
pnpm add use-context-hook
실시간 예시
- 문자열 셀렉터:
- 배열 셀렉터:
- 객체 셀렉터:
- Redux‑Style 함수 셀렉터:
대안과 비교
| 라이브러리 | 번들 크기 | 셀렉터 패턴 | TypeScript | 재렌더링 방지 |
|---|---|---|---|---|
| use-context-hook | ~2 KB | 4가지 패턴 | 전체 지원 | 예 |
| use-context-selector | ~3 KB | 함수만 | 부분 지원 | 예 |
| React Context (내장) | 0 KB | 없음 | 내장 | 아니오 |
| Redux | ~15 KB+ | 지원 | 지원 | 예 (하지만 무겁다) |
마무리
React Context는 강력하지만 기본 동작은 큰 앱에서 불필요한 재렌더링을 초래할 수 있습니다. use-context-hook은 다음을 제공합니다:
- 작음 – 최소화된 ~2 KB
- 빠름 – 선택된 값에만 깊은 비교 수행
- 타입 안전 – 완전한 TypeScript 지원
- 유연함 – 네 가지 셀렉터 패턴
- 의존성 제로 – React만 피어 의존성으로 사용
Redux‑style 선택적 구독을 복잡성 없이 구현하고 싶다면 활용해 보세요.
링크
- npm 패키지:
- GitHub 저장소:
- 전체 문서: