react

[React]리엑트 살펴보기7: context API에 대해

tea-tea 2024. 3. 11. 00:19

 

contextAPI는 전역에 사용될 데이터를 공유하는데 사용된다. 이는 리엑트의 단방향 데이터 흐름 구조에서 부모 state의 데이터를 props로 자식에게 전달해줄 때 발생하는 문제를 해결하는 데 도움이 된다.

 

1. props drilling

컴포넌트의 계층이 깊어지고 구조가 복잡해질수록, 부모의 데이터를 props로 자식에게 전달해주기 위해 중간다리 역할만 하는 컴포넌트가 생겨난다. 그리고 전달해줄 props는 점점 더 많아진다. 이런 문제를 props drilling이라 부른다. 이는 컴포넌트의 독립성을 해치고 가독성을 떨어뜨린다.
contextAPI는 그런 문제에 대한 대안중 하나로서 전역에 상태를 제공할 때 유용하게 사용된다.

 

2. 방법

import React, { useReducer } from "react";
import Counter from "./Counter";

export const GlobalContext = React.createContext<any>(null);

function App() {

  return (
    <GlobalContext.Provider value={"i'm global data"}>
      <div>
        <Child />
      </div>
    </GlobalContext.Provider>
  );
}

export default App;

 

  1. 먼저 React.createContext()로 전역에 공유할 맥락을 생성한다. 인자는 맥락이 공유할 데이터의 초기 값이다.
  2. React.createContext().Provider로 맥락을 공유할 컴포넌트를 감싸준다. 
  3. value props로 공유할 맥락 값을 할당한다.
  4. 만약 provider로 감싸지 않은 컴포넌트에서 useContext를 사용하여 기존에 만든 context에 접근한다면, 초기 값이 반환된다.

 

//@ Child.jsx
import {GlobalContext} from ./ App.tsx


function Child() {
  const  globalValue= useContext(GlobalContext);
  return (
  <div>{globalValue}</div>
  );
}

export default App;

 

    1. useContext 훅의 인자로 아까 만든 맥락(React.createContext())을 가져온다. 이러면 props를 통하지 않고 전역으로 데이터를 보낼 수 있다.
    2. 이 값은 상태값 이외에도 원시 값이나 객체 모두 가능하다.

 

3. 리렌더링

context provider를 통해 제공된 value가 변화할 때 마다 provider 내에서 useContext를 사용한 컴포넌트는 리렌더링 된다. 이 때문에, context api는 자주 변환되는 복잡한 객체 값을 관리할 시 하위 컴포넌트의 잦은 리렌더링을 일으켜서 성능 이슈를 만들 수 있다. 게다가 그 데이터의 아주 작은 부분만 구독해도 문제를 일으킨다.

 

 

대안1. context provider 분할: 관심사의 분리

context가 관리하는 데이터를 분할하는 것이다. 이러면, 해당 데이터를 구독하는 컴포넌트만 리렌더링될 것이다.

 

function CounterProvider1({ children }) {
  const [counterState, setCounterState] = useState(1);
  return (
    <>
      <CounterContext.Provider value={counterState}>
        <setCounterContext.Provider value={setCounterState}>
          {children}
        </setCounterContext.Provider>
      </CounterContext.Provider>
    </>
  );
}

function App() {
  return (
    <div className="App">
      <CounterProvider1>
        <ModalButton />
   		<ModalView />
      </CounterProvider1>
    </div>
  );
}

function ModalButton() {
  const setCounter = useContext(setCounterContext);
  return (
    <button
      className="ModalButton"
      onClick={() => setCounter((prev) => prev + 1)}
    > click
    </button>
  );
}

function ModalView() {
  const counter = useContext(CounterContext);
  console.log("modal view");
  return <div className="ModalView">view:{counter}</div>;
}

export default ModalView;
  • 위 예시에서는 counterState라는 useState 훅을 getter와 setter로 분리하여 각각 modalView와 modalButton으로 전달하였다.
  • 이러면 modalView는 업데이트 되어도 modalButton은 업데이트 되지 않는다.

하지만, 이런 기법은 전역 상태가 간단할 때만 분리가 용이하다. 만약 관리해야하는 값이 크고 복잡할수록, provider을 분리하면서 가독성이 떨어질 것이다.

그래서 context api는 자주 변경되지 않는 데이터를 전역에 사용할 때 쓰는 것이 좋다.

 

4. 용도

  • 전역에서 사용되는 데이터를 공유할 때 props drilling 없이 사용가능하다.(ex: 테마 정보)
  • 단, 상태관리 라이브러처럼 사용하는 건 지양해야 한다. 이걸로 상태를 변경할 시, Provider로 감싸진 컴포넌트의 재렌더링이 발생하는 문제 때문이다.