프로젝트 캠프: Next.js 과정 2기

[유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 2기 - 사전직무교육 2주차 - Context

hanlabong 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