[2024.06.19] TIL 41일차

김선민·2024년 6월 19일

useContext

useContext 란?

  • context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.

  • Context란?

    • 전역적인 데이터를 전달하기에 편리함
    • App 안에서 전역적으로 사용되는 데이터들을 여러 컴포넌트들끼리 쉽게 공유할 수 있는 방법 제공
    • Props를 일일이 전해주는 것이 아니라 상위 컴포넌트에서 필요한 컴포넌트에게 전해줄 수 있게됨

Context를 사용할 때 Props가 필요한 이유

  • Context를 사용하면 컴포넌트 재사용하기 어려워질 수 있다.
    • Context는 꼭 필요할때만 사용해야 함
    • Props drilling을 피하기 위한 목적이라면 Component Composition(컴포넌트 합성)을 먼저 고려

Context 세가지 개념

  • contextAPI를 사용하기 위해서는 Provider, Consumer, createContext 개념을 알고있어야 한다.

    • createContext : context 객체를 생성한다.
      • export const ThemeContext = createContext(초기값);
      • createContext 내부는 초기값이 들어간다.
        • 상위에서 Provider로 감싸주지 않았다면 초기값을 받아와서 사용하는 것이다.
    • Provider : 생성한 context를 하위 컴포넌트에게 전달하는 역할을 한다.
      • Children을 감싸고 전달하고자 하는 값을 value로 넣어준다.
    • Consumer : context의 변화를 감시하는 컴포넌트이다.

참조

useContext(SomeContext))

  • 컴포넌트 최상위 레벨에서 useContext를 호출하여 context를 읽고 구독한다.
      import { useContext } from 'react';

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

매개변수

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

반환값

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

주의사항

  1. 컴포넌트의 useContext() 호출은 동일한 컴포넌트에서 반환된 provider의 영향을 받지 않는다.

    • 해당 <Context.Provider>는 반드시 useContext() 호출을 수행하는 컴포넌트 위에 있어야 함
  2. React는 변경된 value를 받는 provider부터 시작해서 해당 context를 사용하는 자식들에 대해서까지 전부 자동으로 리렌더링 한다.

    • 이전 값과 다음 값은 Object.js로 비교함
    • memo로 리렌더링을 건너뛰어도 새로운 context 값을 수신하는 자식들을 막지는 못함
  3. 빌드 시스템이 출력 결과에 중복 모듈을 생성하는 경우 context가 손상될 수 있다.

    • context를 통해 무언가 전달하는 것은 === 비교에 의해 결정되는 것처럼 context를 제공하는 데 사용하는 SomeContext와 context를 읽는 데 사용하는 SomeConetext 가 정확하세 동일한 객체인 경우에만 작동함

사용법

트리 깊숙이 데이터 전달하기

  • 컴포넌트의 최상위 레벨에서 useContext를 호출하여 context를 읽고 구독한다.
    import { useContext } from 'react';

    function Button() {
      const theme = useContext(ThemeContext);
      // ...
  • useContext는 전달한 context에 대한 context 값을 반환한다.
  • context 값을 결정하기 위해 React는 컴포넌트 트리를 검색하고 특정 context에 대해 위에서 가장 가까운 context provider를 찾는다.
    function MyPage() {
      return (
        <ThemeContext.Provider value="dark">
          <Form />
        </ThemeContext.Provider>
      );
    }

    function Form() {
      // ... renders buttons inside ...
    }
  • provider와 Button 사이에 얼마나 많은 컴포넌트 레이어가 있는지는 중요하지 않다.
  • From 내부의 Button이 useContext(ThemeContext)를 호출하면 "dark"를 값으로 받는다.

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 값을 받게된다
  • provider에게 전달한 theme 값을 업데이트하기 위해 setTheme를 호출하면 모든 Button 컴포넌트가 새로운 light 값으로 리렌더링된다.

fallback 기본값 지정하기

  • React가 부모 트리에서 특정 context의 provider들을 찾을 수 없는 경우, useContext()가 반환하는 context 값은 해당 context를 생성할 때 지정한 기본값과 동일하다.
	const ThemeContext = createContext(null);
  • 기본값은 절대 변경되지 않는다
    • context를 업데이트하려면 앞서 설명된 대로 state를 사용해야 함
  • null 대신 기본값으로 사용할 수 있는 더 의미 있는 값이 있는 경우가 많다.
	const ThemeContext = createContext('light');
  • 이렇게 하면 실수로 해당 provider 없이 일부 컴포넌트를 렌더링해도 중단되지 않는다.
    • 또한 테스트 환경에서 많은 provider를 설정하지 않고도 컴포넌트가 테스트 환경에서 잘 작동하는 데 도움이 된다.

트리 일부에 대한 context 재정의하기

  • 트리 일부분을 다른 값의 provider로 감싸 해당 부분에 대한 context를 재정의할 수 있다.
    <ThemeContext.Provider value="dark">
      ...
      <ThemeContext.Provider value="light">
        <Footer />
      </ThemeContext.Provider>
      ...
    </ThemeContext.Provider>
``

객체 및 함수 전달 시 리렌더링 최적화

  • 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 값은 두 개의 프로퍼티를 가진 Js 객체이며, 그 중 하나는 함수다.
  • MyApp이 리렌더링할 때마다, 이것은 다른 함수를 가리키는 다른 객체가 될 것이므로 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(AuthProvider)를 호출하는 컴포넌트는 리렌더링할 필요가 없다
























profile
웹 프론트엔드

0개의 댓글