[React] Context Hooks, useContext

0

React

목록 보기
1/6

Context Hooks

컴포넌트가 프롭스(props)를 전달하지 않고 먼 부모(parent) 컴포넌트로부터 정보를 받을 수 있도록 하는 것을 "컨텍스트(Context)"라고 합니다. 예를 들어, 앱의 최상위 컴포넌트는 얼마나 깊은 위치에 있던 하위 모든 컴포넌트에 대해 현재 UI 테마를 전달할 수 있습니다.

  • useContext는 컨텍스트를 읽고 구독(subscribe)합니다.
function Button() {
  const theme = useContext(ThemeContext);
  // ...

useContext

useContext는 컴포넌트에서 컨텍스트를 읽고 구독(subscribe)할 수 있도록 하는 React Hook입니다.

const value = useContext(SomeContext)

Reference

useContext(SomeContext)

컨텍스트를 읽고 구독하려면 컴포넌트의 최상위 수준에서 useContext를 호출하세요.

import { useContext } from 'react';

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

Parameters

  • SomeContext: 이전에 createContext로 생성한 컨텍스트입니다. 컨텍스트 자체는 정보를 보유하지 않으며, 컴포넌트에서 제공하거나 읽을 수 있는 정보의 유형을 나타냅니다.

Returns

useContext는 호출하는 컴포넌트의 컨텍스트 값을 반환합니다. 이 값은 호출 컴포넌트의 가장 가까운 조상인 SomeContext.Provider에서 전달된 값으로 결정됩니다. 만약 해당 프로바이더가 없는 경우, 해당 컨텍스트에 대해 createContext에 전달한 defaultValue 값이 반환됩니다. 반환된 값은 항상 최신 상태입니다. React는 컨텍스트를 읽는 컴포넌트가 해당 값이 변경되면 자동으로 다시 렌더링됩니다.

Caveats

  • 컴포넌트에서 useContext()를 호출하면 해당 컴포넌트에서 반환된 프로바이더에 영향을받지 않습니다. 즉, useContext()를 호출하는 컴포넌트보다 <Context.Provider>가 먼저 선언되어 있어야 합니다.
  • React는 다른 값이 전달되는 프로바이더부터 시작하여 해당 컨텍스트를 사용하는 모든 하위 컴포넌트를 자동으로 다시 렌더링합니다. 이전 값과 다음 값을 Object.is 비교를 통해 비교합니다. memo를 사용하여 재 렌더링을 건너 뛰어도 하위 컴포넌트는 새로운 컨텍스트 값을 수신합니다.
  • 빌드 시스템이 출력에서 중복 모듈을 생성하는 경우 (심볼릭 링크로 인해 발생할 수 있음), 컨텍스트가 중단될 수 있습니다. 즉, 컨텍스트를 통해 값을 전달하는 경우, 컨텍스트를 제공하고 읽는 SomeContext가 동일한 객체여야 하며 === 비교를 통해 확인할 수 있어야 합니다.

Usage

Passing data deeply into the tree

컨텍스트를 읽고 구독하려면 컴포넌트의 최상위 수준에서 useContext를 호출하세요.

import { useContext } from 'react';

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

useContext는 전달한 컨텍스트에 대한 컨텍스트 값을 반환합니다. 컨텍스트 값을 결정하기 위해 React는 컴포넌트 트리를 탐색하고 해당 컨텍스트에 대한 가장 가까운 컨텍스트 프로바이더를 찾습니다.

Button 컴포넌트에 컨텍스트를 전달하려면, 해당 컨텍스트 프로바이더를 감싸거나 부모 컴포넌트 중 하나를 감싸세요.

예시)

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

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

프로바이더와 Button 사이에 몇 개의 컴포넌트가 있더라도 상관없습니다. Form 내부의 Button에서 useContext(ThemeContext)를 호출하면, "dark"를 값으로 받게 됩니다.


❗️Pitfall

useContext()는 항상 호출한 컴포넌트의 상위 프로바이더 중 가장 가까운 것을 찾습니다. 즉, useContext()를 호출하는 컴포넌트에서 프로바이더를 사용하는 경우, 해당 프로바이더는 고려하지 않습니다.


Updating data passed via context

보통 컨텍스트가 시간이 지남에 따라 변경되도록 원합니다. 컨텍스트를 업데이트하려면, 상태(state)와 함께 사용합니다. 부모 컴포넌트에서 상태 변수를 선언하고 현재 상태를 프로바이더의 컨텍스트 값으로 전달하세요.

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

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

Specifying a fallback default value

React가 부모 트리에서 해당 컨텍스트의 프로바이더를 찾을 수 없는 경우, useContext()로 반환된 컨텍스트 값은 해당 컨텍스트를 생성할 때 지정한 기본값과 동일합니다

예시)

const ThemeContext = createContext(null);

기본값은 항상 변경되지 않습니다. 컨텍스트를 업데이트하려면 위에서 설명한대로 상태와 함께 사용하세요.

일반적으로 null 대신 더 의미있는 값을 기본값으로 사용할 수 있습니다.

예시)

const ThemeContext = createContext('light');

이렇게 하면 실수로 프로바이더를 빼먹어도 컴포넌트가 오류를 내지 않습니다. 또한, 테스트 환경에서 많은 프로바이더를 설정하지 않고도 컴포넌트가 잘 작동하도록 할 수 있습니다.

Overriding context for a part of the tree

트리의 일부에 대해 컨텍스트를 재정의하려면, 해당 부분을 다른 값으로 갖는 프로바이더로 감싸면 됩니다.

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

필요한 만큼 프로바이더를 중첩하고 재정의할 수 있습니다.

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

위 예시에서 컨텍스트 값은 함수를 포함하는 JavaScript 객체입니다. MyApp이 다시 렌더링 될 때마다(예: 라우트 업데이트) 다른 함수를 가리키는 다른 객체가됩니다. 그러므로 React는 useContext(AuthContext)를 호출하는 트리의 깊숙한 모든 컴포넌트를 다시 렌더링해야 합니다.

작은 앱에서는 이것이 문제가 되지 않을 수 있습니다. 그러나 currentUser와 같은 기본 데이터가 변경되지 않은 경우, 다시 렌더링할 필요가 없습니다. React가 이 사실을 활용할 수 있도록 useCallback으로 login 함수를 감싸고 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이 다시 렌더링되더라도, useContext(AuthContext)를 호출하는 컴포넌트는 currentUser가 변경되지 않는 한 다시 렌더링할 필요가 없습니다.

출처:

https://react.dev/reference/react/useContext#passing-data-deeply-into-the-tree

profile
지치지 않는 백엔드 개발자 김성주입니다 :)

0개의 댓글