카테고리 없음

react정리6

jkkll 2022. 7. 23. 12:56

장바구니 context 추가

context 추가 위해 componenets옆에 store폴더 추가

cart-context.js파일 추가

import React from "react";

//아이템 배열, totalAmount초기값0, 추가아이템함수, 지우는함수
const CartContext = React.createContext({
  items: [],
  totalAmount: 0,
  addItem: (item) => {},
  removeItem: (id) => {},
});

export default CartContext;

CartProvider.js 파일 추가

import CartContext from "./cart-context";

//CartContext import후 provider에 접근
//cartContext도우미 상수를 이 컴포넌트함수에 추가
//단순히 items와 totalAmount로 된 객체
const CartProvider = (props) => {
  const addItemToCartHandler = (item) => {};

  const removeItemFromCartHandler = (id) => {};

  //함수를 만들고 가리키도록 설정
  const cartContext = {
    items: [],
    totalAmount: 0,
    addItem: addItemToCartHandler,
    removeItem: removeItemFromCartHandler,
  };

  return (
    <CartContext.Provider value={cartContext}>
      {props.children}
    </CartContext.Provider>
  );
};

export default CartProvider;

장바구니에 접근하는 모든 컴포넌트를 CartProvider로 감싸준다

App.js

import { Fragment, useState } from "react";
import Header from "./components/Layout/Header";
import Meals from "./components/Meals/Meals";
import Cart from "./components/Cart/Cart";
import CartProvider from "./store/CartProvider";

function App() {
  const [cartIsShown, setCartIsShown] = useState(false);
  const showCartHandler = () => {
    setCartIsShown(true);
  };

  const hideCartHandler = () => {
    setCartIsShown(false);
  };

  return (
    <CartProvider>
      {cartIsShown && <Cart onClose={hideCartHandler} />}
      <Header onShowCart={showCartHandler} />
      <main>
        <Meals />
      </main>
    </CartProvider>
  );
}

export default App;

 

Context사용

HeaderCartButton.js에서

useContext import

CartContext import

useContex호출해서 CartContext전달

상수 cartCtx에 저장

import { useContext } from "react";

import CartIcon from "../Cart/CartIcon";
import classes from "./HeaderCartButton.module.css";
import CartContext from "../../store/cart-context";

const HeaderCartButton = (props) => {
  const cartCtx = useContext(CartContext);
  //아이템갯수 목록 한개에 갯수만 추가하기 위해 reducer사용
  //reduce는 내장 메소드 데이터배열을 값 하나로 변환해준다
  //첫번째 인수는 함수, 두번째 인수는 시작값
  //curNumber(현재수량) 사용자지정이름
  //item 현재 보고있는 항목
  //현재수량에 아이템의 숫자를 더한다
  //그 값이 리턴되고..
  //밑에 리턴에서 적용시킨다 함수이름을
  const numberOfCartItems = cartCtx.items.length.reduce((curNumber, item) => {
    return curNumber + item.amount;
  }, 0);

  return (
    <button className={classes.button} onClick={props.onClick}>
      <span className={classes.icon}>
        <CartIcon />
      </span>
      <span>Your Cart</span>
      <span className={classes.badge}>{numberOfCartItems}</span>
    </button>
  );
};

export default HeaderCartButton;

 

장바구니 항목 추가

CartProvider.js로 이동

import CartContext from "./cart-context";
import { useReducer } from "react";

//리듀서 함수에서 리턴할 새 state 스냅샷을 위해  리듀서 밖에 함수 생성
//그리고 이 함수를 cartReducer에서 반환
//cartProvider에서 useReducer호출
const defaultCartState = {
  items: [],
  totalAmount: 0,
};
//state는 최신 state스냅샷
//장바구니 항목을 추가하기 위한 추가로직 설정
//어떤 타입인지 확인 위해 if 타입체크

const cartReducer = (state, action) => {
  //타입이 ADD일 경우 여기에서 장바구니 항목 업데이트
  //같은 항목은 같이 그룹화해서 수량관리, total값도 업데이트
  //concat은 자바스크립트 메소드로 기존 배열을 편집하는 게 아니라 새 배열을 반환
  //얻게될 action.item을 concat한다
  //업데이트된 토탈 가격 이전 state스냅샷의 totalAmount에 price, amount 곱한것들 더한다
  //얻게될 item에 amount필드와 price필드가 있을 것으로 예상
  //새로운 state반환
  if (action.type === "ADD") {
    const updatedItems = state.items.concat(action.item);
    const updatedTotalAmount =
      state.totalAmount + action.item.price * action.item.amount;
    return {
      items: updatedItems,
      totalAmount: updatedTotalAmount,
    };
  }
  return defaultCartState;
};
//CartContext import후 provider에 접근
//cartContext도우미 상수를 이 컴포넌트함수에 추가
//단순히 items와 totalAmount로 된 객체
const CartProvider = (props) => {
  //()의 첫번째 인수로 리듀서 함수 가리키고, 초기 state설정하는데 만들어둔 상수 재사용
  //[]배열 첫번째 요소는 항상 state스냅샷 이름은 사용자설정, 두번째요소는 함수 이걸 통해 리듀서에 액션전달 사용자설정이름
  const [cartState, dispatchCartAction] = useReducer(
    cartReducer,
    defaultCartState
  );
  //이 함수가 호출될 때마다 장바구니 추가할 항목을 얻는다
  //장바구니에 같은 항목이 있는지 확인하고, 있다면 갯수 업데이트
  //없다면 새 항목을 추가하도록 만든다
  //state관리를 위해 useState나 useReducer를 사용하는데 여기서 사용시 복잡하기 때문에 reducer사용
  //useReducer설정 후 장바구니 액션을 정의할 수 있는데
  //useReducer의 액션을 정의하는 것은 전적으로 사용자에게 달려있다
  //숫자나 텍스트일 수도 있지만 일반적으로 객체
  //어떤 속성을 가지고 있고 이를 통해 해당 액션을 식별한다
  //속성추가 후 두번째 속성에 항목전달 item이라고 속성이름 정하고 item을 가리키도록 설정
  const addItemToCartHandler = (item) => {
    dispatchCartAction({ type: "ADD", item: item });
  };
  //액션타입이 2가지가 됨 위에 add와 remove
  const removeItemFromCartHandler = (id) => {
    dispatchCartAction({ type: "REMOVE", id: id });
  };

  //함수를 만들고 가리키도록 설정
  //reducer함수 다 설정한 후 items에서 빈 배열 대신 cartState.items에 접근하도록 설정
  //item은 state로 cartState.totalAmount로 관리
  const cartContext = {
    items: cartState.items,
    totalAmount: cartState.totalAmount,
    addItem: addItemToCartHandler,
    removeItem: removeItemFromCartHandler,
  };

  return (
    <CartContext.Provider value={cartContext}>
      {props.children}
    </CartContext.Provider>
  );
};

export default CartProvider;

장바구니 항목 출력

Cart.js

//모든 장바구니 아이템 랜더링, 총 수량 표시
import { useContext } from "react";
import Modal from "../UI/Modal";

import CartItem from "./CartItem";
import classes from "./Cart.module.css";
import CartContext from "../../store/cart-context";

//cartCtx.items에 접근
const Cart = (props) => {
  const cartCtx = useContext(CartContext);
  //toFixed 소수점2자리까지 표시
  const totalAmount = `$${cartCtx.totalAmount.toFixed(2)}`;
  const hasItems = cartCtx.items.length > 0;

  //제거함수 삭제할 항목의 id를 얻음
  const cartItemRemoveHandler = (id) => {};
  //항목 자체를 가져올 함수
  const cartItemAddHandler = (item) => {};

  //카트에 표시되는 아이템 설정위해 컴포넌트 따로만들어진 거에 key값추가
  //bind(null, item.id) 추가되거나 삭제된 항목의 id가 remove헨들러로 전달된다
  const cartItems = (
    <ul className={classes["cart-items"]}>
      {cartCtx.items.map((item) => (
        <CartItem
          key={item.id}
          name={item.name}
          amount={item.amount}
          price={item.price}
          onRemove={cartItemRemoveHandler.bind(null, item.id)}
          onAdd={cartItemAddHandler.bind(null, item.id)}
        />
      ))}
    </ul>
  );
  return (
    <Modal onClose={props.onClose}>
      {cartItems}
      <div className={classes.total}>
        <spam>Total Amount</spam>
        <span>{totalAmount}</span>
      </div>
      <div className={classes.actions}>
        <button className={classes["button--alt"]} onClick={props.onClose}>
          Close
        </button>
        {hasItems && <button className={classes.button}>Order</button>}
      </div>
    </Modal>
  );
};

export default Cart;

장바구니 안에서도 항목을 추가 및 삭제하기

import CartContext from "./cart-context";
import { useReducer } from "react";

const defaultCartState = {
  items: [],
  totalAmount: 0,
};

const cartReducer = (state, action) => {
  if (action.type === "ADD") {
    const updatedTotalAmount =
      state.totalAmount + action.item.price * action.item.amount;

    //findeIndex() 배열에서 항목의 인덱스를 찾아준다
    //item.id가 action.item.id와 같은 경우 true반환
    //현재배열에서 보고있는 항목이 전달된 액션으로 추가하는 항목과 동일한 id를 가지는 경우
    //해당 항목이 존재한다면 해당항목의 인덱스를 돌려준다
    const existingCartItemIndex = state.items.findIndex(
      (item) => item.id === action.item.id
    );

    const existingCartItem = state.items[existingCartItemIndex];

    let updatedItems;
    //카트에 아이템이 있을경우 항목복사 수량 업데이트
    //액션에 의해 추가된 수량을 더해준다
    if (existingCartItem) {
      const updatedItem = {
        ...existingCartItem,
        amount: existingCartItem.amount + action.item.amount,
      };
      updatedItems = [...state.items];
      updatedItems[existingCartItemIndex] = updatedItem;
    } else {
      updatedItems = state.items.concat(action.item);
    }

    return {
      items: updatedItems,
      totalAmount: updatedTotalAmount,
    };
  }
  //action 타입이 remove일 경우의 타입체크
  //장바구니에서 -버튼 누르면 제거하기
  if (action.type === "REMOVE") {
    const existingCartItemIndex = state.items.findIndex(
      (item) => item.id === action.id
    );
    const existingItem = state.items[existingCartItemIndex];
    const updatedTotalAmount = state.totalAmount - existingItem.price;
    let updatedItems;
    //수량이 1개일 경우 버튼클릭시 항목까지 삭제
    //특정 id를 가진 항목 삭제
    //else경우 항목을 그대로 두고 수량만 줄인다
    if (existingItem.amount === 1) {
      updatedItems = state.items.filter((item) => item.id !== action.id);
    } else {
      //수량 업데이트 -로
      const updatedItem = { ...existingItem, amount: existingItem.amount - 1 };
      updatedItems = [...state.items];
      updatedItems[existingCartItemIndex] = updatedItem;
    }

    return {
      items: updatedItems,
      totalAmount: updatedTotalAmount,
    };
  }

  return defaultCartState;
};

//CartContext import후 provider에 접근
//cartContext도우미 상수를 이 컴포넌트함수에 추가
//단순히 items와 totalAmount로 된 객체
const CartProvider = (props) => {
  //()의 첫번째 인수로 리듀서 함수 가리키고, 초기 state설정하는데 만들어둔 상수 재사용
  //[]배열 첫번째 요소는 항상 state스냅샷 이름은 사용자설정, 두번째요소는 함수 이걸 통해 리듀서에 액션전달 사용자설정이름
  const [cartState, dispatchCartAction] = useReducer(
    cartReducer,
    defaultCartState
  );
  const addItemToCartHandler = (item) => {
    dispatchCartAction({ type: "ADD", item: item });
  };
  //액션타입 2가지 add와 remove
  const removeItemFromCartHandler = (id) => {
    dispatchCartAction({ type: "REMOVE", id: id });
  };

  //함수를 만들고 가리키도록 설정
  //reducer함수 다 설정한 후 items에서 빈 배열 대신 cartState.items에 접근하도록 설정
  //item은 state로 cartState.totalAmount로 관리하기 때문에 저렇게 적어줌
  const cartContext = {
    items: cartState.items,
    totalAmount: cartState.totalAmount,
    addItem: addItemToCartHandler,
    removeItem: removeItemFromCartHandler,
  };

  return (
    <CartContext.Provider value={cartContext}>
      {props.children}
    </CartContext.Provider>
  );
};

export default CartProvider;

 

useEffect사용

import { useContext, useEffect, useState } from "react";

import CartIcon from "../Cart/CartIcon";
import classes from "./HeaderCartButton.module.css";
import CartContext from "../../store/cart-context";

const HeaderCartButton = (props) => {
  //버튼클릭시 애니메이션 효과 실행 useEffect, useState사용
  const [btnIsHiglighted, setBtnIsHighlighted] = useState(false);
  const cartCtx = useContext(CartContext);
  //객체로 지정해준다
  const { items } = cartCtx;

  const numberOfCartItems = items.reduce((curNumber, item) => {
    return curNumber + item.amount;
  }, 0);

  //클래스들을 const로 묶어준다 버튼에 적용
  const btnClasses = `${classes.button} ${btnIsHiglighted ? classes.bump : ""}`;

  useEffect(() => {
    if (items.length === 0) {
      return;
    }
    setBtnIsHighlighted(true);

    //클래스가 추가된 상태에서 한 번만 실행되기 때문에
    //클래스를 삭제해야한다
    //300ms타이머설정(애니메이션 재생속도 CSS에서 설정한)
    //setBtnIsHighlighted가 false가 되면 btnClasses에서 빈 문자열이 된다
    //300ms후에 클래스가 삭제된다(애니메이션이 재생되는 속도)
    const timer = setTimeout(() => {
      setBtnIsHighlighted(false);
    }, 300);

    //clean up function 위해 timer라는 상수부분 추가 const timer
    return () => {
      clearTimeout(timer);
    };
  }, [items]);

  return (
    <button className={btnClasses} onClick={props.onClick}>
      <span className={classes.icon}>
        <CartIcon />
      </span>
      <span>Your Cart</span>
      <span className={classes.badge}>{numberOfCartItems}</span>
    </button>
  );
};

export default HeaderCartButton;