본문 바로가기
코딩 정보/React

[React] context api를 이용해서 props drilling 막기

by 꽁이꽁설꽁돌 2024. 7. 14.
728x90
반응형

목차

     

    프롭스 드릴링이란?

    컴포넌트 트리에서 데이터를 하위 컴포넌트로 전달하기 위해 중간 컴포넌트를 통해 프로퍼티를 내려주는 것을 의미한다.
    이러한 중간 컴포넌트는 원하는 자식 컴포넌트에게 프로퍼티를 전달하기 위해 필요하지만 해당 값을 직접 사용하지 않는 경우에도 프로퍼티를 받고 전달해야 하는 비효율적인 문제가 생긴다.

     

    그렇다면 프롭스 드릴링은 어떻게 막을 수 있을까?

     

    1. 컴포넌트 구조를 바꾸기

     

    바꾸기 전 코드

    Shop.jsx

    import { DUMMY_PRODUCTS } from "../dummy-products.js";
    import Product from "./Product";
    
    export default function Shop({ onAddItemToCart }) {
      return (
        <section id="shop">
          <h2>Elegant Clothing For Everyone</h2>
    
          <ul id="products">
            {DUMMY_PRODUCTS.map((product) => (
              <li key={product.id}>
                <Product {...product} onAddToCart={onAddItemToCart} />
              </li>
            ))}
          </ul>
        </section>
      );
    }

     

    App.jsx

    import { useState } from "react";
    
    import Header from "./components/Header";
    import Shop from "./components/Shop";
    import { DUMMY_PRODUCTS } from "./dummy-products.js";
    
    function App() {
      const [shoppingCart, setShoppingCart] = useState({
        items: [],
      });
    
      function handleAddItemToCart(id) {
        ...
      }
    
      return (
        <>
          <Shop onAddItemToCart={handleAddItemToCart} />
        </>
      );
    }
    
    export default App;

     

    방법은 children속성을 이용하여 직접 App컴포넌트에서 넘겨주는 것이다.

     

    Shop.jsx

    import { DUMMY_PRODUCTS } from "../dummy-products.js";
    import Product from "./Product";
    
    
    //children을 이용해서 바꾼 코드
    export default function Shop({ children }) {
      return (
        <section id="shop">
          <h2>Elegant Clothing For Everyone</h2>
          {children}
          <ul id="products"></ul>
        </section>
      );
    }

     

    App.jsx

    import { useState } from "react";
    import Product from "./components/Product";  //추가한 부분
    import Header from "./components/Header";
    import Shop from "./components/Shop";
    import { DUMMY_PRODUCTS } from "./dummy-products.js";
    
    function App() {
      const [shoppingCart, setShoppingCart] = useState({
        items: [],
      });
    
      function handleAddItemToCart(id) {
        ...
      }
    
      return (
        <>
          <Shop>
            {DUMMY_PRODUCTS.map((product) => (
              <li key={product.id}>
                <Product {...product} onAddToCart={handleAddItemToCart} />
              </li>
            ))}
          </Shop>
        </>
      );
    }
    
    export default App;

     

    이렇게 바꿀 경우 여전히 app component가 비대해지는 문제가 있다.

    그래서 다음 해결책은 context api를 이용한 방법이다.

     

    2. context api 이용하기

    context api를 사용하는 방법을 알아보자

     

    shopping-cart-context.jsx

    import { createContext } from "react";
    import { useState } from "react";
    
    //쓸 변수와 함수를 정의해 준다.
    export const CartContext = createContext({
      items: [],  //초기 item값
      
      //가짜 함수를 만들어 줌으로써 자동완성 기능에 도움을 준다.
      addItemToCart: () => {},
      updateItemToCart: () => {},
    });

     

    아래 코드와 같이 CartContext.Provider로 context 쓸 부분을 묶어준다.

    App.jsx

    import { useState } from "react";
    import Product from "./components/Product";  //추가한 부분
    import Header from "./components/Header";
    import Shop from "./components/Shop";
    import { DUMMY_PRODUCTS } from "./dummy-products.js";
    
    function App() {
      const [shoppingCart, setShoppingCart] = useState({
        items: [],
      });
    
      function handleAddItemToCart(id) {
        ...
      }
      
      //이런식으로 객체로 묶어줌으로써 provider에서 함수도 사용가능하게 한다.
       const ctxValue = {
        items: shoppingCart.items,
        addItemToCart: handleAddItemToCart,
        updateItemToCart: handleUpdateCartItemQuantity,
      };
    
      return (
      	//초기 value를 state와 연결함으로써 동적으로 작동한다.
        <CartContext.Provider value={ctxValue}>
          <Shop>
            {DUMMY_PRODUCTS.map((product) => (
              <li key={product.id}>
                <Product {...product} onAddToCart={handleAddItemToCart} />
              </li>
            ))}
          </Shop>
        </CartContext.Provider>
      );
    }
    
    export default App;

     

    이렇게 하면 쓸 준비는 끝났다. 다음은 이것을 이용하여 다른 component에서 내가 만든 context를 써보자

    방법1. useContext 사용하기

    아래와 같이 사용할 수 있다.

    import { useContext } from "react";
    import { CartContext } from "../store/shopping-cart-context";
    
    //useContext, CartContext를 불러준다.
    
    
    export default function Product({ id, image, title, price, description }) {
      //객체를 구조분해 해준다.
      const { addItemToCart } = useContext(CartContext);
      return (
        <article className="product">
          <img src={image} alt={title} />
          <div className="product-content">
            <div>
              <h3>{title}</h3>
              <p className="product-price">${price}</p>
              <p>{description}</p>
            </div>
            <p className="product-actions">
              <button onClick={() => addItemToCart(id)}>Add to Cart</button>
            </p>
          </div>
        </article>
      );
    }

     

    방법2. Consumer사용하기

    import { useContext } from "react";
    import { CartContext } from "../store/shopping-cart-context";
    
    export default function Product({ id, image, title, price, description }) {
      const { addItemToCart } = useContext(CartContext);
      return (
        <CartContext.Consumer>
          {({ addItemToCart }) => {
            return (
              <article className="product">
                <img src={image} alt={title} />
                <div className="product-content">
                  <div>
                    <h3>{title}</h3>
                    <p className="product-price">${price}</p>
                    <p>{description}</p>
                  </div>
                  <p className="product-actions">
                    <button onClick={() => addItemToCart(id)}>Add to Cart</button>
                  </p>
                </div>
              </article>
            );
          }}
        </CartContext.Consumer>
      );
    }

     

    이렇게 context를 사용했음에도 여전히 함수들과 객체로 묶어준 초기 동적 value때문에

    App 컴포넌트가 비대하다는 문제점이 있다. 어떻게 하면 해결할 수 있을까?

     

    비대한 app 컴포넌트

    import { useState } from "react";
    import Product from "./components/Product";  //추가한 부분
    import Header from "./components/Header";
    import Shop from "./components/Shop";
    import { DUMMY_PRODUCTS } from "./dummy-products.js";
    
    function App() {
      const [shoppingCart, setShoppingCart] = useState({
        items: [],
      });
    
      function handleAddItemToCart(id) {
        ...
      }
       function handleUpdateCartItemQuantity(productId, amount) {
        ...
      }
      
      //이런식으로 객체로 묶어줌으로써 provider에서 함수도 사용가능하게 한다.
       const ctxValue = {
        items: shoppingCart.items,
        addItemToCart: handleAddItemToCart,
        updateItemToCart: handleUpdateCartItemQuantity,
      };
    
      return (
      	//초기 value를 state와 연결함으로써 동적으로 작동한다.
        <CartContext.Provider value={ctxValue}>
          <Shop>
            {DUMMY_PRODUCTS.map((product) => (
              <li key={product.id}>
                <Product {...product} onAddToCart={handleAddItemToCart} />
              </li>
            ))}
          </Shop>
        </CartContext.Provider>
      );
    }
    
    export default App;

     

     

    바로 context컴포넌트에 함수를 담고 children속성을 이용해 주는 것이다!

     

    수정한 shopping-cart-context.jsx

    import { createContext } from "react";
    import { useState } from "react";
    import { DUMMY_PRODUCTS } from "../dummy-products";
    import { useReducer } from "react";
    
    export const CartContext = createContext({
      items: [],
      addItemToCart: () => {},
      updateItemToCart: () => {},
    });
    
    
    export default function CartContextProvider({children}) {
    
      function handleAddItemToCart(id) {
    	...
      }
    
      function handleUpdateCartItemQuantity(productId, amount) {
        ...
      }
    
      const ctxValue = {
        items: shoppingCart.items,
        addItemToCart: handleAddItemToCart,
        updateItemToCart: handleUpdateCartItemQuantity,
      };
    
      return <CartContext.Provider value={ctxValue}>{children}</CartContext.Provider>;
    
    }

     

    훨씬 간결해진 app.jsx 컴포넌트

    import { useState } from "react";
    
    import Header from "./components/Header.jsx";
    import Shop from "./components/Shop.jsx";
    import { DUMMY_PRODUCTS } from "./dummy-products.js";
    import Product from "./components/Product.jsx";
    import CartContextProvider from "./store/shopping-cart-context.jsx";
    function App() {
      
    
      return (
        <CartContextProvider>
          <Header/>
          <Shop>
            {DUMMY_PRODUCTS.map((product) => (
              <li key={product.id}>
                <Product {...product} />
              </li>
            ))}
          </Shop>
        </CartContextProvider>
      );
    }
    
    export default App;

     

     

    반응형