본문 바로가기

프론트엔드/React

[React] 컨텍스트(context API)

반응형

[React] 컨텍스(useContext)

1. useState로 상태관리 코드 구현

import { useState } from "react";
import Home from "./components/Home";
import Navbar from "./components/Navbar";

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <Navbar count={count} setCount={setCount} />
      <Home count={count} />
    </>
  );
}
export default function Home({ count }: { count: number }) {
  return (
    <>
      <h1>Home Component {count}</h1>
    </>
  );
}
import Banner from "./Banner";

export type CountProps = {
  count: number;
  setCount: React.Dispatch<React.SetStateAction<number>>;
};

export default function Navbar({ count, setCount }: CountProps) {
  return (
    <>
      <h1>Navbar Component: {count}</h1>
      <Banner count={count} setCount={setCount} />
    </>
  );
}

import { CountProps } from "./Navbar";

export default function Banner({ count, setCount }: CountProps) {
  const handleIncrement = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <h1>Banner Component: {count}</h1>
      <button onClick={handleIncrement}>증가</button>
    </>
  );
}

 

App컴포넌트에 정의되어 있는 상태를 Banner 하위 컴포넌트에서 사용하기 위해서 값을 App ⇨ Navbar ⇨ Banner 로 Props를 전달해주고 있습니다. 이렇게 하위 컴포넌트 계층 구조가 깊어지는 과정을 Props Drilling이라고 합니다.

컨텍스트는 props 전달없이 context에서 지원해주는 값을 가져와서 사용할 수가 있습니다.

 

 

2. contextAPI 코드 구현

import { createContext, useState } from "react";
import Home from "./components/Home";
import Navbar from "./components/Navbar";

type CountContext = {
  count: number;
  setCount: React.Dispatch<React.SetStateAction<number>>;
};

export const CountContext = createContext<CountContext | null>(null);

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <CountContext.Provider value={{ count, setCount }}>
      <Navbar />
      <Home />
    </CountContext.Provider>
  );
}
  • CountContext.Provider로 감싼 value 값을 자식 컴포넌트에 전달됩니다.
  • CountContext 타입은 createContext 제네릭 타입으로 명시합니다.
  • Context를 외부에서 참조할 수 있도록 export 키워드로 내보내기 합니다.
import { useContext } from "react";
import { CountContext } from "../App";

export default function Banner() {
  const countContext = useContext(CountContext);
  console.log(countContext);

...

  • countContext 변수가 정상 호출되고 있습니다.
import { useContext } from "react";
import { CountContext } from "../App";

export default function Banner() {
  const countContext = useContext(CountContext);

  const handleIncrement = () => {
    countContext?.setCount((prev) => prev + 1);
  };

  return (
    <>
      <h1>Banner Component: {countContext?.count}</h1>
      <button onClick={handleIncrement}>증가</button>
    </>
  );
}
  • 초기값에 null이 있어서 counContext ?. 연산자 코드를 작성해줍니다.
import { useContext } from "react";
import { CountContext } from "../App";

export default function Home() {
  const countContext = useContext(CountContext);

  return (
    <>
      <h1>Home Component {countContext?.count}</h1>
    </>
  );
}
import Banner from "./Banner";

export default function Navbar() {
  return (
    <>
      <h1>Navbar Component:</h1>
      <Banner />
    </>
  );
}

  • contextAPI 사용해서 value값이 잘 출력되고 있습니다.

 

 

3. createContext 초기값을 넣어주 구조분해 할당하기

export const CountContext = createContext<CountContext>({
  count: 0,
  setCount: () => {},
});

export default function App() {
...
import { useContext } from "react";
import { CountContext } from "../App";

export default function Banner() {
  const { count, setCount } = useContext(CountContext);

  const handleIncrement = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <h1>Banner Component: {count}</h1>
      <button onClick={handleIncrement}>증가</button>
    </>
  );
}

 

 

4. Banner 에서만 context 호출하는 코드로 수정하

export default function Home() {
  console.log("Home Component rendered");

  return (
    <>
      <h1>Home Component</h1>
    </>
  );
}

import Banner from "./Banner";

export default function Navbar() {
  console.log("Navbar Component rendered");

  return (
    <>
      <h1>Navbar Component:</h1>
      <Banner />
    </>
  );
}

import { useContext } from "react";
import { CountContext } from "../App";

export default function Banner() {
  console.log("Banner Component rendered");
  const { count, setCount } = useContext(CountContext);

  const handleIncrement = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <h1>Banner Component: {count}</h1>
      <button onClick={handleIncrement}>증가</button>
    </>
  );
}

최초에 렌더된 콘솔로그가 출력되되고 증가 버튼을 누를 때마다 모든 컴포넌트가 리렌더링 되고 있는 문제가 있어요.!

이유는 App 컴포넌트의 코드 방식으로 자식 컴포넌트를 context의 Provider로 감쌌을 때 모두 리렌더링이 되고 있기 때문입니다.

import { createContext, useState } from "react";
import Home from "./components/Home";
import Navbar from "./components/Navbar";

type CountContext = {
  count: number;
  setCount: React.Dispatch<React.SetStateAction<number>>;
};

export const CountContext = createContext<CountContext>({
  count: 0,
  setCount: () => {},
});

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <CountContext.Provider value={{ count, setCount }}>
      <Navbar />
      <Home />
    </CountContext.Provider>
  );
}
  • 모든 자식컴포넌트에 리렌더링이 일어나는 이유는 context가 감싸고 있는 컴포넌트는 value 변수의 변경된 count 값을 모두 새로 적용하기 때문입니다. 그래서 count 상태를 사용하지 않는 컴포넌트도 모두 리렌더링이 됩니다.

 

 

5. 별도의 context 컴포넌트로 나눠 사용하기

import { createContext, useState } from "react";

type CountContext = {
  count: number;
  setCount: React.Dispatch<React.SetStateAction<number>>;
};

export const CountContext = createContext<CountContext>({
  count: 0,
  setCount: () => {},
});

export const CounterProvider = ({
  children,
}: {
  children: React.ReactNode;
}) => {
  const [count, setCount] = useState(0);
  return (
    <CountContext.Provider value={{ count, setCount }}>
      {children}
    </CountContext.Provider>
  );
};

  • 컨텍스트 컴포넌트를 새롭게 만들어줍니다.
import Home from "./components/Home";
import Navbar from "./components/Navbar";
import { CounterProvider } from "./context/CountContext";

export default function App() {
  return (
    <CounterProvider>
      <Navbar />
      <Home />
    </CounterProvider>
  );
}

  • 새로 만든 중간 컴포넌트 콘텍스트를 wrapping 합니다.
import { useContext } from "react";
import { CountContext } from "../context/CountContext";

export default function Banner() {
  console.log("Banner Component rendered");
  const { count, setCount } = useContext(CountContext);

  const handleIncrement = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <h1>Banner Component: {count}</h1>
      <button onClick={handleIncrement}>증가</button>
    </>
  );
}

  • 새로 만든 context로 import 받아올게요.

이제 Banner 컴포넌트만 리렌더링 됩니다.

 

return (
  <CountContext.Provider value={{ count, setCount }}>
    <Navbar />
  </CountContext.Provider>
  <CountContext.Provider value={{ count, setCount }}>
    <Home />
  </CountContext.Provider>
  <CountContext.Provider value={{ count, setCount }}>
    <Banner />
  </CountContext.Provider>
  );
};

각각의 컴포넌트에 중간 컴포넌트가 개별 wrapping 되게 동작하기 때문입니다.

이렇게 중간 컴포넌트 context를 만들어줘야 불필요한 렌더링이 일어나지 않습니다.

반응형