반응형

🚀 Context

일반적으로 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 데이터를 전달하는데, 만약 그 깊이가 깊어질수록 거쳐가야 하는 컴포넌트들이 많아지고 코드를 반복적으로 작성해야 하며 변수명이 바뀌면 거쳐가는 모든 컴포넌트에서 변수명을 수정해야 하는 등등.. 꽤나 비효율적인 일들이 발생할 수 도 있습니다.

하지만 Context를 사용하면 전역적으로 데이터를 공유하기 때문에 중간 다리 역할만 하는 컴포넌트들을 건너뛰고 데이터가 필요한 컴포넌트에서 바로 사용이 가능하게 됩니다. 리액트 Hook인 useContext는 이러한 Context를 좀 더 편하게 사용할 수 있게 해주는 역할을 하는데, 우선 Context API의 개념에 대해 짚고 넘어가겠습니다.

 

Context API 3가지 주요 개념

createContext(initialValue)
  • context 객체 생성
  • createContext 함수 호출 시 Provider와 Consumer 컴포넌트 반환
  • initialValue는 Provider를 사용하지 않았을 때 적용될 초기값을 의미
Context.Provider

 

  • 생성한 context를 하위 컴포넌트에 전달하는 역할
Context.Consumer

// useContext 사용 시
const data = useContext(Context);
  • context의 변화를 감시하는 컴포넌트
  • 설정한 상태를 불러올 때 사용

 

useContext 예제

App.jsx에 있는 품목 데이터와 품목을 추가하는 함수를 props로 App=>Mart=>Emart=>Emart3F 컴포넌트로 내려줘서 해당 품목들의 정보를 출력하는 예제입니다. (props drilling 패턴)

먼저 useContext를 사용하지 않고 예제를 구현해보자면!

useContext 미적용

// App.js

import React, { useState } from "react";
import Mart from "./components/Mart";

const initialItems = {
  drink: [
    { name: "Powerade", price: "1800" },
    { name: "Coca Cola", price: "1100" },
  ],
  fruits: [
    { name: "Apple", price: "3500" },
    { name: "Grape", price: "6000" },
  ],
};

function App() {
  const [items, setItem] = useState(initialItems);

  const addDrink = (newDrink) => {
    setItem({ ...items, drink: [...items.drink, newDrink] });
    console.log(newDrink.name + "가 추가되었습니다.");
  };

  return (
    <div style={{ position: "absolute", top: "5%", left: "5%" }}>
      <Mart items={items} addDrink={addDrink} />
    </div>
  );
}
//addDrink={addDrink}
export default App;
// ./components/Mart.jsx

import React from "react";
import Emart from "./Emart";

function Mart({ items, addDrink }) {
  return (
    <div>
      <h1>마트</h1>
      <Emart items={items} addDrink={addDrink}></Emart>
    </div>
  );
}

export default Mart;
// ./components/Emart.jsx

import React from "react";
import Emart3F from "./Emart3F";

function Emart({ items, addDrink }) {
  return (
    <div>
      <h2>이마트</h2>
      <Emart3F items={items} addDrink={addDrink}></Emart3F>
    </div>
  );
}

export default Emart;
// ./components/Emart3F.jsx

import React, { useContext } from "react";
import { AppContext } from "../App";

function Emart3F({ items, addDrink }) {
  const newDrink = { name: "HOT6", price: "1500" };
  return (
    <div>
      <h3>이마트 3층</h3>
      <div>
        음료수
        {items.drink.map((item, index) => {
          return (
            <div key={index}>
              {item.name} : {item.price}원
            </div>
          );
        })}
        <button onClick={() => addDrink(newDrink)}>음료 품목 추가</button>
      </div>

      <div>
        과일
        {items.fruits.map((item, index) => {
          return (
            <div key={index}>
              {item.name} : {item.price}원
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default Emart3F;

App 컴포넌트에서 Emart3F 컴포넌트까지 props를 내려주는데 중간에 Mart, Emart 컴포넌트는 단순히 중간다리 역할만 하고 있습니다. 만약 하위 컴포넌트의 개수가 수십 개라면 이러한 방법은 매우 비효율적일 것이고 유지보수 또한 어려워질 것입니다.

 

useContext 적용

// App.js

import React, { useState, createContext } from "react";
import Mart from "./components/Mart";

export const AppContext = createContext();

const initialItems = {
  drink: [
    { name: "Powerade", price: "1800" },
    { name: "Coca Cola", price: "1100" },
  ],
  fruits: [
    { name: "Apple", price: "3500" },
    { name: "Grape", price: "6000" },
  ],
};

function App() {
  const [items, setItem] = useState(initialItems);

  const addDrink = (newDrink) => {
    setItem({ ...items, drink: [...items.drink, newDrink] });
    console.log(newDrink.name + "가 추가되었습니다.");
  };

  return (
    <div style={{ position: "absolute", top: "5%", left: "5%" }}>
      <AppContext.Provider value={{ items, addDrink }}>
        <Mart />
      </AppContext.Provider>
    </div>
  );
}
export default App;
// ./components/Mart.jsx

import React from "react";
import Emart from "./Emart";

function Mart() {
  return (
    <div>
      <h1>마트</h1>
      <Emart></Emart>
    </div>
  );
}

export default Mart;
// ./components/Emart.jsx

import React from "react";
import Emart3F from "./Emart3F";

function Emart() {
  return (
    <div>
      <h2>이마트</h2>
      <Emart3F></Emart3F>
    </div>
  );
}

export default Emart;
// ./components/Emart3F.jsx

import React, { useContext } from "react";
import { AppContext } from "../App";

function Emart3F() {
  const newDrink = { name: "HOT6", price: "1500" };
  const { items, addDrink } = useContext(AppContext);
  return (
    <div>
      <h3>이마트 3층</h3>
      <div>
        음료수
        {items.drink.map((item, index) => {
          return (
            <div key={index}>
              {item.name} : {item.price}원
            </div>
          );
        })}
        <button onClick={() => addDrink(newDrink)}>음료 품목 추가</button>
      </div>

      <div>
        과일
        {items.fruits.map((item, index) => {
          return (
            <div key={index}>
              {item.name} : {item.price}원
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default Emart3F;

useContext를 적용함으로써 Mart, Emart 컴포넌트는 props를 하위 컴포넌트로 전달하는 중간다리 역할을 하지 않아도 됩니다.

 

useContext를 사용할 때 주의해야 할 점은 Provider에서 제공한 value가 달라진다면 useContext를 사용하고 있는 모든 컴포넌트가 리렌더링 된다는 점입니다. 따라서 useContext를 사용할 때 value 부분을 메모제이션 하는데에 신경을 써줘야 합니다.

 

 

반응형

+ Recent posts