useContext

Chaerin Kim·2023년 12월 4일

컴포넌트에서 context를 읽고 구독할 수 있는 React Hook

const value = useContext(SomeContext)

Reference

useContext(SomeContext)

컴포넌트의 최상위 수준에서 useContext를 호출하여 context를 읽고 구독할 수 있음.

import { useContext } from 'react';

function MyComponent() {
  const theme = useContext(ThemeContext);
  
  // ...

Parameters

  • someContext: 이전에 createContext로 생성한 context입니다. Context 자체는 정보를 보유하지 않으며, 컴포넌트에서 제공하거나 읽을 수 있는 정보의 종류를 나타낼 뿐.

Returns

useContext는 호출하는 컴포넌트의 context 값을 반환함. 이 값은 트리에서 호출 컴포넌트 위에 있는 가장 가까운 SomeContext.Provider에 전달된 값으로 결정됨. 그러한 provider가 없는 경우 반환되는 값은 해당 context에 대해 createContext에 전달한 defaultValue가 됨. 반환된 값은 항상 최신 값임. React는 context가 변경되면 context를 읽는 컴포넌트를 자동으로 다시 렌더링함.

Caveats

  • 컴포넌트의 useContext() 호출은 동일한 컴포넌트에서 반환된 provider의 영향을 받지 않음. 해당 <Context.Provider>useContext() 호출을 수행하는 컴포넌트 위에 있어야 함.
  • React는 다른 value를 받는 provider부터 시작해서 특정 context를 사용하는 모든 자식들을 자동으로 다시 렌더링함. 이전 값과 다음 값은 Object.is를 통해 비교됨. memo로 재렌더링을 건너뛰어도 자식들이 새로운 context 값을 받는 것을 막지는 못함.
  • 빌드 시스템에서 결과로 중복 모듈을 생성하는 경우(symlinks에서 발생할 수 있음) context가 손상될 수 있음. Context를 통해 무언가를 전달하는 것은 context를 제공하는 데 사용하는 someContext와 context를 읽는 데 사용하는 someContext=== 비교에 의해 결정되는 정확히 동일한 객체인 경우에만 작동함.

Usage

Passing data deeply into the tree

컴포넌트의 최상위 수준에서 useContext를 호출하여 context를 읽고 구독할 수 있음.

import { useContext } from 'react';

function Button() {
  const theme = useContext(ThemeContext);
  
  // ...

useContext는 전달한 context에 대한 context 값을 반환함. Context 값을 결정하기 위해 React는 컴포넌트 트리를 검색하고 특정 context에 대해 가장 가까운 상위 context provider를 찾음.

Context를 Button에 전달하려면 Button 또는 그 부모 컴포넌트 중 하나를 해당 context provider로 감쌀 것:

function MyPage() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  );
}

function Form() {
  
  // ... renders buttons inside ...
}

Provider와 Button 사이에 얼마나 많은 컴포넌트 레이어가 있는지는 중요하지 않음. Form 내부의 버튼이 useContext(ThemeContext)를 호출하면 "dark"를 값으로 받음.

Pitfall

useContext()는 항상 이를 호출하는 컴포넌트 '상위'에서 가장 가까운 provider를 찾음. useContext()를 호출하는 컴포넌트 내의 provider는 고려하지 않음.

Updating data passed via context

시간이 지남에 따라 context가 변경되기를 원하는 경우가 종종 있음. Context를 업데이트하려면 context를 state와 결합할 것. 부모 컴포넌트에서 state 변수를 선언하고 현재 state를 context 값으로 provider에게 전달하면 됨.

function MyPage() {
  const [theme, setTheme] = useState('dark');
  return (
    <ThemeContext.Provider value={theme}>
      <Form />
      <Button onClick={() => {
        setTheme('light');
      }}>
        Switch to light theme
      </Button>
    </ThemeContext.Provider>
  );
}

이제 provider 내부의 모든 Button이 현재 theme 값을 받게 됨. setTheme을 호출하여 provider에게 전달한 theme 값을 업데이트하면 모든 Button 컴포넌트가 새로운 'light' 값으로 다시 렌더링됨.

Specifying a fallback default value

React가 부모 트리에서 특정 context의 provider를 찾을 수 없는 경우, useContext()가 반환하는 context 값은 해당 context를 생성할 때 지정한 기본값과 같음:

const ThemeContext = createContext(null);

기본값은 변경되지 않음. Context를 업데이트하려면 위에서 설명한 대로 state와 함께 사용해야 함.

예를 들어, null 대신 더 의미 있는 값을 기본값으로 사용할 수 있는 경우가 종종 있음:

const ThemeContext = createContext('light');

이렇게 하면 실수로 해당 provider 없이 일부 컴포넌트를 렌더링해도 깨지지 않음. 또한 테스트 환경에서 많은 provider를 설정하지 않고도 컴포넌트가 잘 작동하도록 도와줌.

Overriding context for a part of the tree

트리의 일부분을 다른 값의 provider로 감싸면 해당 부분에 대한 context를 재정의할 수 있음.

<ThemeContext.Provider value="dark">
  ...
  
  <ThemeContext.Provider value="light">
    <Footer />
  </ThemeContext.Provider>
  
  ...
</ThemeContext.Provider>

필요한 만큼 provider를 중첩하고 재정의할 수 있음.

Optimizing re-renders when passing objects and functions

Context를 통해 객체와 함수를 포함한 모든 값을 전달할 수 있음.

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>
  );
}

여기서 context 값은 두 개의 프로퍼티를 가진 JavaScript 객체이며, 그 중 하나는 함수임. 이 객체는 MyApp이 route 업데이트 등의 이유로 다시 렌더링될 때마다 다른 함수를 가리키는 다른 객체가 될 것이므로, React는 useContext(AuthContext)를 호출하는 트리 깊숙한 곳의 모든 컴포넌트를 다시 렌더링해야함.

작은 앱에서는 문제가 되지 않기는 하지만, currentUser와 같은 기본 데이터가 변경되지 않았다면 다시 렌더링할 필요가 없음. React가 이 사실을 활용하도록 돕기 위해 login 함수를 useCallback으로 감싸고 객체 생성을 useMemo로 감쌀 수 있음. 이것은 성능 최적화를 위한 것:

import { useCallback, useMemo } from 'react';

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

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

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

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

이렇게 변경하면, MyApp이 다시 렌더링되어야 하는 경우에도 currentUser가 변경되지 않는 한 useContext(AuthContext)를 호출하는 컴포넌트는 다시 렌더링할 필요가 없음.

참고: useCallback, useMemo


Troubleshooting

My component doesn’t see the value from my provider

이러한 일이 발생하는 몇 가지 일반적인 상황이 있음:

  1. useContext()를 호출하는 곳과 동일한 컴포넌트(또는 그 아래)에서 <SomeContext.Provider>를 렌더링하는 경우. <SomeContext.Provider>useContext()를 호출하는 컴포넌트의 위와 바깥으로 이동할 것.
  2. 컴포넌트를 <SomeContext.Provider>로 감싸는 것을 잊었거나 생각했던 것과 다른 트리 부분에 넣었을 경우. React 개발자 도구를 사용하여 계층 구조가 올바른지 확인할 것.
  3. Tooling에서 빌드 문제가 발생하여 provider 컴포넌트에서 보는 SomeContext와 reading 컴포넌트에서 보는 SomeContext가 두 개의 다른 객체가 될 수 있음. 예를 들어, symlinks를 사용하는 경우 이런 문제가 발생할 수 있음. 이를 확인하려면 각 SomeContextwindow.SomeContext1window.SomeContext2처럼 전역에 할당하고 콘솔에서 window.SomeContext1 === window.SomeContext2인지 확인하면 됨. 동일하지 않은 경우 빌드 tool 수준에서 해당 문제를 수정할 것.

I am always getting undefined from my context although the default value is different

트리에 value가 없는 provider가 있을 수 있음:

// 🚩 Doesn't work: no value prop
<ThemeContext.Provider>
   <Button />
</ThemeContext.Provider>

value를 지정하는 것을 잊어버리면 value={undefined}를 전달하는 것과 같음.

실수로 다른 prop 이름을 잘못 사용했을 수도 있음:

// 🚩 Doesn't work: prop should be called "value"
<ThemeContext.Provider theme={theme}>
   <Button />
</ThemeContext.Provider>

이 두 가지 경우 모두 콘솔에서 React의 경고가 표시될 것. 이를 수정하려면 prop value을 호출할 것:

// ✅ Passing the value prop
<ThemeContext.Provider value={theme}>
   <Button />
</ThemeContext.Provider>

createContext(defaultValue) 호출의 기본값은 위에 일치하는 provider가 전혀 없는 경우에만 사용된다는 점에 유의할 것. 부모 트리 어딘가에 <SomeContext.Provider value={undefined}> 컴포넌트가 있는 경우, useContext(SomeContext)를 호출하는 컴포넌트는 undefined를 context 값으로 받음.

0개의 댓글