본문으로 건너뛰기
개발 머꼬
개발 노트React
hohyeon.dev21

Context 값 하나만 바꿨는데 관련 없는 컴포넌트까지 전부 리렌더링된 이유

  • #Engineering Note
  • #Performance
  • #React

문제 발생

사용자 정보와 UI 테마를 하나의 Context에 함께 담아 관리했는데, 테마만 바꿨을 뿐인데 사용자 정보만 쓰는 것처럼 보이는 멀리 떨어진 컴포넌트들까지 전부 리렌더링되는 것을 성능 프로파일러에서 확인했습니다.

const AppContext = createContext({ user: null, theme: "light" });

function UserBadge() {
  const { user } = useContext(AppContext); // theme엔 관심 없음
  return <span>{user?.name}</span>;
}

원인 분석

React의 Context는 값 전체 단위로 구독됩니다 — useContext(AppContext)를 호출하는 컴포넌트는 AppContextvalue가 (참조 기준으로) 바뀔 때마다 리렌더링됩니다. 그 컴포넌트가 실제로 그 값의 어느 필드를 쓰는지는 React가 전혀 알지 못합니다 — UserBadgetheme을 전혀 참조하지 않아도, value 객체 자체가 새로 만들어졌다면(테마가 바뀌어 { user, theme: "dark" }라는 새 객체가 만들어졌다면) UserBadge도 리렌더링 대상이 됩니다.

이건 최적화가 덜 된 게 아니라 Context API의 근본적인 설계입니다 — Context는 애초에 "세밀한 구독"이 아니라 "값 전체를 트리 깊숙이 전달"하는 것을 목적으로 만들어졌습니다.

해결 방안

  1. 성격이 다른 상태는 별도의 Context로 분리합니다 — 이게 가장 직접적이고 흔히 권장되는 해법입니다.
const UserContext = createContext(null);
const ThemeContext = createContext("light");

이렇게 나누면 theme이 바뀌어도 UserContext의 값은 그대로이므로 UserContext만 구독하는 컴포넌트는 리렌더링되지 않습니다.

  1. Context를 나누기 어려운 상황이라면, 리렌더링되는 하위 트리 자체를 React.memo로 감싸 props가 실제로 안 바뀐 부분은 건너뛰게 할 수 있습니다 — 다만 이건 Context 리렌더링을 막는 게 아니라, 그 리렌더링이 자식으로 전파되는 것만 막아준다는 점에 주의합니다.
  2. useContext로 값 전체를 받은 뒤 컴포넌트 안에서 useMemo로 필요한 부분만 골라 쓰는 것은 리렌더링 자체를 막지는 못합니다 — 컴포넌트 함수가 다시 호출되는 것(그리고 그 안에서 훅이 다시 실행되는 것) 자체는 여전히 일어납니다. 진짜 리렌더링을 막으려면 구독 범위를 Context 분리로 좁혀야 합니다.
  3. 상태 관리 라이브러리(Zustand, Jotai 등)는 애초에 "선택자(selector) 기반 세분화 구독"을 지원하도록 설계되어 있어, Context보다 이런 문제에서 자유로운 경우가 많습니다 — 상태가 자주 바뀌고 구독 범위가 세밀하게 갈리는 상황이라면 Context 대신 이런 라이브러리를 검토할 만합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.