ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 2기 - 사전직무교육 2주차 - Context
    프로젝트 캠프: Next.js 과정 2기 2024. 7. 26. 02:07
    728x90

    💡 props drilling을 해결하기 위해 나온 함수로 데이터를 관리하는 곳을 따로 만들고 사용하여 props로 데이터를 전달하지 않아도 자식 컴포넌트가 부모 컴포넌트의 상태를 참조/업데이트할 수 있게 도와준다.

    언제 사용 할까?

    context는 리액트 컴포넌트 트리 안에서 전역적으로 사용할 수 있는 데이터를 공유할 수 있도록 나온 기능이다. 따라서

    1. 로그인한 유저
    2. 테마
    3. 선호하는 언어

    등과 같이 앱 안에서 전역적으로 사용되는 데이터에 사용하는 경우가 많다.

    기본 형태

    const StateContext = createContext(기본값);

    컴포넌트 외부에서 createContext를 호출해 커텍스트를 사용합니다.

    • 기본값: 컴포넌트가 컨텍스트를 읽을 때 상위에 일치하는 컨텍스트 제공자가 없는 경우 컨텍스트가 가져야 할 값
      • 기본값이 없는 경우 null 지정
      • 시간이 지나도 변하지 않음.

    컨텍스트 자체는 어떠한 정보도 가지고 있지 않으며, 다른 컴포넌트가 읽거나 제공하는 어떤 컨텍스트를 나타낸다.

    • StateContext.Provider
      • 상위 컴포넌트에서 컨텍스트 값을 지정
    • StateContext.Consumer
      • 하위 컴포넌트에서 데이터를 읽기 위해 호출
      • 드물게 사용됨

    사용 방법

    예제 코드

    실행영상보기

    import { useState } from 'react';
    import './App.css';
    import Page from './Page';
    
    function App() {
      const [isLightMode, setIsLightMode] = useState(true);
    
      return (
        <>
          <Page isLightMode={isLightMode} setIsLightMode={setIsLightMode} />
        </>
      );
    }
    
    export default App;
    import ThemeButton from './ThemeButton';
    import TopBar from './TopBar';
    
    interface IPageProps {
      isLightMode: boolean;
      setIsLightMode: React.Dispatch<React.SetStateAction<boolean>>;
    }
    
    const Page = ({ isLightMode, setIsLightMode }: IPageProps) => {
      return (
        <>
          <TopBar isLightMode={isLightMode} />
          <ThemeButton setIsLightMode={setIsLightMode} />
        </>
      );
    };
    
    export default Page;

     

    이렇게 라이트모드/다크모드를 구분할 수 있는 상태를 props로 계속 아래로 넘겨주고 있다. 현재는 컴포넌트의 깊이가 깊은 편은 아니지만 만약 여기서 더 깊어진다면 상태변화로 인해 재렌더링되는 컴포넌트의 수가 많아져 성능적인 문제가 발생할 수 있다.

    context 적용

    export const ThemeContext = createContext<{
      isLightMode: boolean;
      setIsLightMode: React.Dispatch<React.SetStateAction<boolean>>;
    }>({ isLightMode: true, setIsLightMode: () => {} });
    • 기본값: { isLightMode: true, setIsLightMode: () => {} }
    • 기본값이 객체이기 때문에 타입스크립트에서는 타입 오류가 발생할 수 있다. 따라서 createContext 옆에 제네릭으로 타입을 지정해줘야 한다.
    import { useState } from 'react';
    import './App.css';
    import Page from './Page';
    import { ThemeContext } from './context/ThemeContext';
    
    function App() {
      const [isLightMode, setIsLightMode] = useState(true);
    
      return (
        <ThemeContext.Provider value={{ isLightMode, setIsLightMode }}>
          <Page />
        </ThemeContext.Provider>
      );
    }
    
    export default App;
    import ThemeButton from './ThemeButton';
    import TopBar from './TopBar';
    
    const Page = () => {
      return (
        <>
          <TopBar />
          <ThemeButton />
        </>
      );
    };
    
    export default Page;
    

     

    이제 이렇게 context의 provider를 적용하면 props로 상태값을 넘겨주지 않아도 된다.

    그럼 데이터를 어떻게 가져와서 사용할 수 있을까?

    const { isLightMode } = useContext(ThemeContext);
    💡 이렇게 useContext라는 훅을 사용해주면 된다!

    memoization

    Page 컴포넌트에 컨텍스트 데이터를 사용하지 않는 컴포넌트를 하나 추가해보도록 하겠다.

    const None = () => {
      console.log('no!!');
      return <>No Contents</>;
    };
    
    export default None;
    import None from './None';
    import ThemeButton from './ThemeButton';
    import TopBar from './TopBar';
    
    const Page = () => {
      return (
        <>
          <TopBar />
          <ThemeButton />
          <None />
        </>
      );
    };
    
    export default Page;

     

    위의 코드를 실행하고 테마를 변동시키면 컨텍스트 데이터를 사용하고 있지 않는 None 컴포넌트도 재렌더링이 되는 것을 확인할 수 있다.

     

    왜??

    상위 컴포넌트인 App 컴포넌트에서 정의한 isLightMode 상태가 업데이트됐기 때문에 하위 컴포넌트인 None도 자동으로 재렌더링된 것이다.

     

    이걸 어떻게 해결할까?

    memoization을 추가하면 된다! 간단하게 React.memo 함수를 사용해도 되지만 그 방법은 생각보다 많은 비용을 요구한다고 한다. 따라서 컨텍스틀 이용해 재렌더링을 방지하는 코드를 작성해보자.

    export const CounterContextProvider = ({
      children,
    }: {
      children: React.ReactNode;
    }) => {
      const [count, setCount] = useState(0);
    
      return (
        <CounterContext.Provider value={{ count, setCount }}>
          {children}
        </CounterContext.Provider>
      );
    };
    import './App.css';
    import Page from './Page';
    import { CounterContextProvider } from './context/ThemeContext';
    
    function App() {
      return (
        <CounterContextProvider>
          <Page />
        </CounterContextProvider>
      );
    }
    
    export default App;

     

    위와 같이 children을 받아서 context provider내부에 넣어주는 함수 컴포넌트를 제작하면 memoization을 사용하지 않아도 재렌더링을 방지할 수 있다.

     

    어떻게 가능한걸까?

    React.memo() 함수는 함수 컴포넌트에서 컴포넌트를 반환하는 고차 컴포넌트 형태이다. 위의 코드 또한 함수 컴포넌트에서 컴포넌트를 반환하는 코드로 작성한 것이기 때문에 React.memo()처럼 동작해 별도로 memoization을 적용하지 않아도 재렌더링을 막을 수 있다.

     

     

    예제 코드

    https://stackblitz.com/edit/vitejs-vite-g5rggb?file=src%2FApp.tsx

    728x90

    댓글

Designed by Tistory.