React19.1 - Context Hooks(useContext)

Hunter Joe·2025년 4월 19일

useContext 컴포넌트에서 Context를 읽고 subscribe할 수 있는 React Hook

<목차>

  • Context
  • useContext

📌 Context

Props는 부모 컴포넌트에서 자식 컴포넌트로 정보를 전달할 때 사용되지만, 여러 컴포넌트를 거치게 될 경우 복잡해질 수 있다. 이럴 때 Context를 사용하면 명시적으로 props를 전달하지 않아도 트리 구조의 하위 컴포넌트들이 해당 props(공통 데이터)에 직접 접근할 수 있다.

The problem with passing props (prop drilling)

위 Context에 대한 설명과 같은 prop drilling에 대한 얘기임
→ props는 깊어질 수 있고 이를 위한 '순간이동'기능이 Context.

Before you use context


Context를 사용하기 전에 다음을 고려하라고 한다.

  1. 우선은 props를 통해 전달하는 방식부터 시작하세요.
    컴포넌트가 단순하지 않다면 여러 개의 props를 여러 단계의 컴포넌트를 거쳐 전달하는 일은 흔하다.
    이 과정이 번거롭게 느껴질 수 있지만 어떤 컴포넌트가 어떤 데이터를 사용하는지 명확하게 보여주기 때문에 유지보수하는 사람 입장에서는 오히려 고마운 일이다.
    props를 통해 데이터 흐름을 명시적으로 표현하는 것도 좋은 방법이다.
  1. 중간 컴포넌트가 props를 안 쓰는데 계속 넘겨주는 상황이면 children 구조로 바꿔서 데이터 전달 경로를 줄이면 된다.
<Layout>
  <Posts posts={posts} />
</Layout>

이 이외의 상황이라면 Context를 고려해도 좋다

📌 useContext

Convention

const value = useContext(SomeContext)

Parameters

  • someContext: createContext로 생성한 context 자체는 실제 데이터를 가지고 있는 것이 아니라 어떤 종류의 데이터를 컴포넌트들이 주고받을 수 있는지를 표현하는 역할만 한다

Returns
useContext는 호출한 컴포넌트에서 사용할 context의 값을 반환한다.
트리 상에서 가장 가까운 <SomeContext.Provider>에 전달된 value에 따라 결정
이 값은 컴포넌트 된다.
만약 트리 상위에 그런 Provider가 없다면, createContext 할 때 지정했던 defaultValue가 반환된다.
또한 반환된 값은 항상 최신 상태이다.
React는 해당 context 값이 변경되면 이를 참조하는 컴포넌트를 자동으로 리렌더링한다.

// Provider 제공 X 
const MyContext = useContext("defaultValue")

// Provider 제공 O 
<MyContext.Provider value="offerByProvider">
  <App/>
</MyContext.Provider>

Caveats

  1. 컴포넌트 내부에서 useContext()를 호출할 경우 그 컴포넌트 안에서 반환된 Provider에는 영향을 받지 않는다.
    useContext()를 사용하는 컴포넌트 위쪽에 위치한 <Context.Provider>여야만 정상적으로 context 값을 제공한다.
  function Example() {
  const value = useContext(MyContext); // 여기선 아직 Provider가 적용 안 됨

  return (
    <MyContext.Provider value="">
      {/* ... */}
    </MyContext.Provider>
  );
}
// Provider는 트리 상에서 위에 있어야 한다.
  1. React는 특정 context 값을 사용하는 모든 하위 컴포넌트를 자동으로 다시 렌더링한다.
    이때, context의 이전 값과 새 값은 Object.is를 기준으로 비교된다.
    React.memo는 context로 인한 리렌더링을 막을 수 없다. → 리렌더링

  2. 빌드 시스템이 (심볼릭 링크 등으로 인해) 중복된 모듈을 만들어내는 경우, context가 정상적으로 동작하지 않을 수 있다.
    context는 제공할 때 사용하는 SomeContext읽을 때 사용하는 SomeContext === 비교로 완전히 동일한 객체일 때만 정상 작동한다.

Usage

아래는 공식문서에서 제공한 useContext에 대한 예제 코드

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

const ThemeContext = createContext(null);

export default function MyApp() {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={theme}>
      <Form />
      <label>
        <input
          type="checkbox"
          checked={theme === 'dark'}
          onChange={(e) => {
            setTheme(e.target.checked ? 'dark' : 'light')
          }}
        />
        Use dark mode
      </label>
    </ThemeContext.Provider>
  )
}

function Form({ children }) {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ children }) {
  const theme = useContext(ThemeContext);
  const className = 'button-' + theme;
  return (
    <button className={className}>
      {children}
    </button>
  );
}

Optimizing re-renders when passing objects and functions(최적화 부분)

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  function login(response) {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }

  return (
    <AuthContext.Provider value={{ currentUser, login }}>
      <Page />
    </AuthContext.Provider>
  );
}

useContext()로 객체나 함수가 들어있는 값을 전달할 때
매번 새로운 객체가 생기면 → 자식 컴포넌트가 불필요하게 리렌더링된다.

이를 방지하기 위해 useCallback,useMemo를 활용해서 자식 컴포넌트의 리렌더링을 최적화할 수 있다.

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  const login = useCallback((response) => {
    setCurrentUser(response.user);
  }, []);

  const contextValue = useMemo(() => ({
    currentUser,
    login,
  }), [currentUser, login]);

  return (
    <AuthContext.Provider value={contextValue}>
      <Page />
    </AuthContext.Provider>
  );
}

MyApp이 리렌더링 되더라도 useContext(AuthContext)를 사용하는 하위 컴포넌트들은
currentUser가 변경되지 않는 한 리렌더링 X

profile
Improvise, Adapt, Overcome

0개의 댓글