context API에 대해서.. useContext..

Dochaki·2024년 12월 4일
1

면접질문, 이론

목록 보기
4/4
post-thumbnail

Context API

  • Context API는 컴포넌트 트리에서 데이터를 전역적으로 공유할 수 있음.
  • 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 props drilling 없이도 데이터를 효율적으로 전달할 수 있음!
  • 상태관리 도구라기 보다는 전역 상태를 공유해주는 기능을 수행하는 도구라고 생각하면 편할 것 같음. 물론 간단한 전역 상태 공유와 데이터 전달이 가능함

props 로만 데이터 전달하면 생기는 문제는?

  • Props driling
    • 컴포넌트 트리에서 데이터를 하위 컴포넌트로 전달하기 위해 중간 컴포넌트를 통해 프로퍼티를 내려주는 것을 의미함
    • 컴포넌트를 한 두개 거치면 괜찮지만 여러 개면 전달 하기도 불편하고 값이 어디서 오는지도 찾기가 힘듬. 이럴 때 Context를 사용하면 좋음

context를 사용해야 할 때는??

  • 테마 데이터 (다크 모드 혹은 라이트 모드)
  • 사용자 데이터 (현재 인증된 사용자, login)
  • local 데이터 (언어 혹은 지역)
  • 자주 업데이트할 필요없는 데이터에 사용
    • 무슨 뜻이냐면 Context API에서 state값을 변경하면 Provider로 감싼 모든 자식 컴포넌트가 리렌더링 됨 ⇒ 이럴 경우 다른 상태관리 도구를 사용하는 것이 좋음
    • 그래서 데이터를 쉽게 전달하고 공유하기 위한 목적이라고 보면 된다.
      ⇒ 전역 변수의 개념으로 생각하는게 좋음

그렇다고 props를 여러 번 넘긴다고 무조건 context를 쓴다?

  • 이건 아님
  • 리액트 공식문서에서는 context를 사용하면 컴포넌트를 재사용하기 어려워지므로 꼭 필요할 때만 쓸 것을 권장한다고 함
  • context보다 컴포넌트 합성(composition)이 더 간단한 해결책일 수 있음

사용하는 방법??

  1. Context 객체를 만든다
    1. createContext를 사용해서.
  2. Context.Provider를 만든다
    1. Provider로 컴포넌트를 감싼다.
  3. Provider의 프로퍼티인 value에 전달할 데이터를 넣는다
  4. 두 가지 방법이 있는데 구버전과 신버전 (쓰고 싶은 거 쓰면 됨)
  • 많은 곳에 useContext 추천함 ⇒ consumer사용하면 많아질수록 코드 가독성 떨어짐

4-1. 구버전 Consumer 컴포넌트를 사용했다.

import { createContext } from ‘react’;

export const themeContext = createContext(전달할 데이터의 초기값);
export default function App() {
  return (
    <themeContext.Provider value={전달할 데이터}>
      <Theme />
    </themeContext.Provider>
  )
}

function Theme() {
  return (
    <themeContext.Consumer>
      {value => <div>{value}</div>}
    </themeContext.Consumer>
  )	
}

4-2. 요즘 방법 useContext() 사용

import { createContext, useContext } from ‘react’;

export const themeContext = createContext(전달할 데이터의 초기값);
export default function App() {
  return (
    <themeContext.Provider value={전달 데이터}>
      <Theme />
    </themeContext.Provider>
  )
}

function Theme() {
  const theme = useContext(themeContext);
  return <div>{theme}</div> // Provider에서 value로 전달한 데이터 출력
}

Context API의 한계

  • 자주 변경되는 상태나 복잡한 로직을 다루는 경우, Context API는 성능상의 문제가 발생할 수 있음.
  • 예를 들어, Context의 value가 변경될 때마다 그 값을 구독하고 있는 모든 하위 컴포넌트들이 리렌더링되고. 이로 인해 대규모 애플리케이션에서 성능 이슈가 발생함.
    • React.memo , useMemo를 사용해 불필요한 리렌더링을 방지할 수 있긴 함
    • React.memo: 컴포넌트 리렌더링을 방지하기 위해 사용
    • useMemo: 불필요한 값 계산을 방지하기 위해 사용
    • useCallback : 함수를 메모이제이션
  • 상태의 변경 로직이나 미들웨어 지원, 상태 변화를 추적하는 기능 등에서는 다른 상태관리 라이브러리가 더 유용!

Context API 최적화

  1. Context를 분리

    1. 관련 데이터를 여러 Context로 나누면 특정 데이터가 변경될 때 다른 데이터에 영향을 주지 않아 불필요한 리렌더링을 줄일 수 있음

    ex) 상태 공급 컨텍스트와 소비 컨텍스트로 분리

    // 공급 컨텍스트 (상태 관리)
    const CounterContext = createContext<number | null>(null);
    
    // 소비 컨텍스트 (상태 변경 함수 관리)
    const CounterActionContext = createContext<{
      increase: () => void;
      decrease: () => void;
      reset: () => void;
    } | null>(null);
  2. useMemo와 useCallback 훅을 활용

    1. 이를 통해 Context 값의 불필요한 재계산을 막음
  3. Context를 사용하는 컴포넌트에 React.memo를 적용

    1. props가 변경되지 않은 경우 리렌더링을 방지해 성능을 높여줌

Todo 다크모드

1. Todo.ts - Context 생성

//Todo.ts
import { createContext } from "react";

export const ThemeContext = createContext<{ isDarkMode: boolean } | null>(
  null
);

export const ThemeActionContext = createContext<
  | {
      toggleDarkMode: () => void;
    }
  | null
>(null);
  • ThemeContext: isDarkMode 값을 저장하는 상태를 위한 컨텍스트
  • ThemeActionContext: 상태를 변경하는 toggleDarkMode 함수가 저장되는 컨텍스트
  • 따로 분리

2. TodoProvider.tsx - Provider 생성


// TodoProvider.tsx
import { useMemo, useState, ReactNode } from "react";
import { ThemeActionContext, ThemeContext } from "../Todo";

export default function TodoProvider({ children }: { children: ReactNode }) {
  const [isDarkMode, setIsDarkMode] = useState(false);

  const toggleDarkMode = () => {
    setIsDarkMode((prev) => !prev);
  };

  const memo = useMemo(() => ({ toggleDarkMode }), []);

  return (
    <>
      <ThemeActionContext.Provider value={memo}>
        <ThemeContext.Provider value={{ isDarkMode }}>
          {children}
        </ThemeContext.Provider>
      </ThemeActionContext.Provider>
    </>
  );
}
  • isDarkMode useState를 통해 다크 모드의 활성화 여부를 저장
  • toggleDarkMode isDarkMode를 토글(반전)
  • useMemo toggleDarkMode 함수 객체를 메모이제이션하여 불필요한 재생성을 방지
  • Provider return 함

3. useTheme.tsx - useContext로 커스텀 훅 생성

// 커스텀 훅
// useTheme.tsx

import { useContext } from "react";
import { ThemeContext, ThemeActionContext } from "./Todo";

export const useTheme = () => {
  const themeContext = useContext(ThemeContext);
  const themeActionContext = useContext(ThemeActionContext);

  if (!themeContext || !themeActionContext) {
    throw new Error("useTheme must be used within a ThemeProvider");
  }

  return { ...themeContext, ...themeActionContext };
};
  • useContext 해서
    • ThemeContext에서 isDarkMode 상태를 가져옴.
    • ThemeActionContext에서 toggleDarkMode 함수를 가져옴
  • 에러 처리
    • useTheme 훅은 반드시 TodoProvider 내에서 호출되어야 하며, 그렇지 않을 경우 에러를 발생시킴
  • 결합된 값 반환
    • { ...themeContext, ...themeActionContext } 형태로 상태와 액션을 결합하여 반환

4. 사용

// TodoHeader.tsx
import { useTheme } from "../context/useTheme";

export default function TodoHeader() {
  const { isDarkMode, toggleDarkMode } = useTheme();

  return (
    <header
      className={`p-4 text-center ${
        isDarkMode ? "bg-gray-800 text-white" : "bg-blue-500 text-black"
      }`}
    >
      <div className="flex justify-between items-center">
        <h1 className="text-2xl font-bold">Todo List</h1>
        <button
          className="bg-gray-300 p-2 rounded hover:bg-gray-400 transition-colors"
          onClick={toggleDarkMode}
        >
          {isDarkMode ? "Light Mode" : "Dark Mode"}
        </button>
      </div>
    </header>
  );
}
//App.tsx
import { useEffect } from "react";
import Todo from "./components/Todo";
import { useTheme } from "./context/useTheme";
import "./css/index.css";
const App = () => {
  const { isDarkMode, toggleDarkMode } = useTheme();

  useEffect(() => {
    document.body.className = isDarkMode ? "dark" : "light";
  }, [isDarkMode]);

  return (
    <div>
      <button onClick={toggleDarkMode}>
        {isDarkMode ? "Switch to Light Mode" : "Switch to Dark Mode"}
      </button>
      <Todo />
    </div>
  );
};

export default App;

5. 감싸기

//main.tsx
import ReactDOM from "react-dom/client";

import App from "./App";
import TodoProvider from "./context/provider/TodoProvider";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <TodoProvider>
    <App />
  </TodoProvider>
);
  1. Despite All That - Tistory
  2. Lee Seohyun's Blog - Tistory
  3. FreeCodeCamp - React Context 완벽 가이드 2021
  4. Velog - React Context API의 개념 및 사용법
  5. Velog - React Context가 꼭 필요할까?
  6. Tistory - React Context API는 왜 쓰고 그럴다면 Redux는 필요없을까
  7. FLAB - React Context API 최적화
  8. Velog - React Context Tutorial

0개의 댓글