Daily Front_Minhhk

[React] Props Drilling + Cmarket Hooks 본문

Code개발일지

[React] Props Drilling + Cmarket Hooks

Minhhk 2022. 12. 27. 21:35

Props Drilling이란

Props Drilling은 상위 컴포넌트의 state를 props를 통해 전달하고자 하는 컴포넌트로 전달하기 위해 그 사이는 props를 전달하는 용도로만 쓰이는 컴포넌트들을 거치면서 데이터를 전달하는 현상을 의미합니다. 위 그림처럼 컴포넌트 A의 state를 컴포넌트 D로 전달하기 위해선 사이에 있는 컴포넌트 B, C를 거쳐야합니다.

 

Props Drilling의 문제점

Props의 전달 횟수가 5회 이내로 많지 않다면 Props Drilling 은 큰 문제가 되지 않습니다. 하지만 규모가 커지고 구조가 복잡해지면서 Props의 전달 과정이 늘어난다면 아래와 같은 문제가 발생합니다.

  • 코드의 가독성이 매우 나빠지게 됩니다.
  • 코드의 유지보수 또한 힘들어지게 됩니다.
  • state 변경시 Props 전달 과정에서 불필요하게 관여된 컴포넌트들 또한 리렌더링이 발생합니다. 따라서, 웹성능에 악영향을 줄 수 있습니다.

상태관리 라이브러리를 사용하게 되면 전역으로 관리하는 저장소에서 직접 state를 꺼내쓸 수 있기 때문에 Props Drilling을 방지하기에 매우 효과적

다양한 상태관리 라이브러리(Redux, Context api, Mobx, Recoil 등) 중 Redux!

 

 

 


 

 

Bare minimum requirements

  • 쇼핑몰 애플리케이션의 주요 기능을 구현
    • [장바구니 담기] 버튼을 이용해 장바구니에 해당 상품이 추가되도록 구현
    • 장바구니 내 [삭제] 버튼을 이용해 장바구니의 상품이 제거되도록 구현
    • 장바구니 내에서 각 아이템 개수를 변경할 수 있도록 구현
    • 장바구니의 상품 개수의 변동이 생길 때마다, 상단 내비게이션 바에 상품 개수가 업데이트 되도록 구현

페이지 전환

페이지 전환은 react-router-dom 이용해 Client Side Routing을 구현

페이지가 전환되지 않는 Single Page App임에도 주소창이 변화

  • <App> 루트 컴포넌트
    • <Router>, <Routes>, <Route>
  • <Nav> 내비게이션 바
    • SPA 내에서 화면 전환에 따라 URL를 업데이트하기 위해 <Link> 컴포넌트를 사용

 

🔥 우선 간단하게 컴포넌트 구조와, 데이터 흐름을 먼저 나타내었다

app.js

function App() {
  // 아이템
  const [items, setItems] = useState(initialState.items);
  // default 장바구니 수량
  const [cartItems, setCartItems] = useState(initialState.cartItems);

  return (
    <Router>
      {/* nav 탭 장바구니 수량 props */}
      <Nav cartItems={cartItems} />
      <Routes>
        {/* cartItems, setCartItems props */}
        <Route
          path="/"
          element={
            <ItemListContainer
              cartItems={cartItems}
              setCartItems={setCartItems}
              items={items}
            />
          }
        />
        {/* cartItems, setCartItems props */}
        <Route
          path="/shoppingcart"
          element={
            <ShoppingCart
              cartItems={cartItems}
              setCartItems={setCartItems}
              items={items}
            />
          }
        />
      </Routes>
      <img
        id="logo_foot"
        src={`${process.env.PUBLIC_URL}/codestates-logo.png`}
        alt="logo_foot"
      />
    </Router>
  );
}

ItemListContain.js

 

item.js 에서 장바구니 담기 버튼에

 

 ➡️ onClick={(e) => handleClick(e, item.id)}

 

이 인자 두개를 넘겨 준다는 것을 확인!

<button *className*="item-button" *onClick*={(e) => handleClick(e, item.id)}>장바구니 담기</button>

 

 

1. find() 사용

function ItemListContainer({ items, cartItems, setCartItems }) {
	if (!cartItems.find((el) => el.itemId === itemId)) {
	      setCartItems([
	        ...cartItems,
	        {
	          itemId: itemId,
	          quantity: 1,
	        },
	      ]);
	    }
	}

 

 

 

2. findIndex() 사용 (장바구니추가 클릭 할 때, 장바구니 페이지에서 수량이 누적됨)

function ItemListContainer({ items, cartItems, setCartItems }) {
  const handleClick = (e, id) => {
    let findIdx = cartItems.findIndex((e) => e.itemId === id);

    if (findIdx !== -1) {
      cartItems[findIdx].quantity += 1;
      setCartItems(cartItems);
    } else {
      setCartItems([...cartItems, { itemId: id, quantity: 1 }]);
    }
  };

클릭을 하면 클릭한 값의 id를 가져와서

if 클릭한 값과 현재 있는 값의 id 를 비교해서 존재하면 quantity += 1 해주고

else 없으면 새로운 값을 기존 cartItems에 이어서

id값과 수량 1개를 셋팅 해준다.

 


 

ShoppingCart.js

export default function ShoppingCart({ items, cartItems, setCartItems }) {
  const [checkedItems, setCheckedItems] = useState(
    cartItems.map((el) => el.itemId)
  );

  // 체크
  const handleCheckChange = (checked, id) => {
    if (checked) {
      setCheckedItems([...checkedItems, id]);
    } else {
      setCheckedItems(checkedItems.filter((el) => el !== id));
    }
  };
  // 올 체크
  const handleAllCheck = (checked) => {
    if (checked) {
      setCheckedItems(cartItems.map((el) => el.itemId));
    } else {
      setCheckedItems([]);
    }
  };

  //TODO : 장바구니 수량 상태 반영 해주기?
  const handleQuantityChange = (quantity, itemId) => {
    //* 기존 아이템
    const item = [...cartItems];
    //* findIndex()로 인덱스 번호 찾고 -> item[인덱스번호].수량 === 수량
    const findIdx = cartItems.findIndex((el) => el.itemId === itemId);
    item[findIdx].quantity = quantity;
    //* useState로 cartItem 상태업뎃ㄱㄱ
    setCartItems(item);
  };

  //TODO : 체크 해제 + 삭제 버튼 눌렀을 때 아이템 삭제
  const handleDelete = (itemId) => {
    //* 체크 해제
    setCheckedItems(checkedItems.filter((el) => el !== itemId));
    //* 삭제 버튼
    setCartItems(cartItems.filter((el) => el.itemId !== itemId));
  };

...생략
}

 

 

➡️ 장바구니 삭제버튼
setCartItems(cartItems.filter((el) => el.itemId !== itemId));

 

 

 

➡️ 장바구니 수량변경

 

  1. findIndex()
  const handleQuantityChange = (quantity, itemId) => {
    //* 기존 아이템
    const item = [...cartItems];
    //* findIndex()로 인덱스 번호 찾고 -> item[인덱스번호].수량 === 수량
    const findIdx = cartItems.findIndex((el) => el.itemId === itemId);
    item[findIdx].quantity = quantity;
    //* useState로 set~ cartItem 상태업뎃ㄱㄱ
    setCartItems(item);
  };
  1. map()
const handleQuantityChange = (quantity, itemId) => {
    setCartItems(
      cartItems.map((el) => {
        if (el.itemId === itemId) {
          return {
            itemId: itemId,
            quantity: quantity,
          };
        } else {
          return el;
        }
      })
    );
  };

 

 

assets/state.js 파일,,

App.js → ShoppingCart.js → props 전달 된 cartItems

"cartItems": [
    {
      "itemId": 1,
      "quantity": 1
    },
    {
      "itemId": 5,
      "quantity": 7
    },
    {
      "itemId": 2,
      "quantity": 3
    }
  ]

Nav.js

function Nav({cartItems}) {

  return (
    <div id="nav-body">
      <span id="title">
        <img id="logo" src="../logo.png" alt="logo" />
        <span id="name">CMarket</span>
      </span>
      <div id="menu">
        <Link to="/">상품리스트</Link>
        <Link to="/shoppingcart">
          {/* app.js 에서 cartItems props -> 길이로 수량 확인 */}
          장바구니<span id="nav-item-counter">{cartItems.length}</span>
        </Link>
      </div>
    </div>
  );
}

Nav({cartItems}) 로 props 받아와서 전달~

 

장바구니 옆 숫자 표시!!

<span id="nav-item-counter">{cartItems.length}</span>

장바구니 옆 <span>에

{cartItems.length} 로 나타내어 수량을 표현 해준다.

 

 


 

구현된 장바구니,,

💡 장바구니의 전체적인 로직을 다 짜지는 못했다.
체크, 모두체크 등등 많이 있었지만 이번 챕터의 목표는 props로 받은 값을 하위 컴포넌트에 전달하여 값을 이용하는 것 이었다.
다음은 redux로 리팩토링이 예상이 되는데,,, 힘내보자!

 

상품리스트 페이지

 

장바구니 페이지