본문 바로가기
  • 삽질하는 자의 블로그
공부용-사이드프로젝트/React - 음식 주문기 프로젝트

2. Context, useReducer 를 사용하여, 카트에 물건을 담아보자.

by 이게뭐당가 2022. 12. 12.

사용 Hook

useContext,

createContext,

useReducer ,

useState

 

javascript 메서드

findIndex,

concat

reduce

function.bind()

 

 

본문

 

카트는, "카트 내부" 뿐 아니라, "메인에 있는 음식 선택창", "음식 선택창의 +, - 버튼" 등

"어디에서나 접근 가능해야한다."

나는 아직 Redux 를 잘 모른다. (현재 진행중이다)

 

그래서 상태관리가 미흡하다. 그러므로   Context   를 사용하여, 카트에 모든 값들을 접근시켜보자.

 

총 프로세스

1. context 를 만든다.
2. context 안에는 useReducer 를 만들어, 추가하고 삭제하는 로직을 만든다.
3. cart 안에서 필요한 상태는, "총가격", "카트 안에 담긴 정보"이다.
4. "카트안에 담길 정보"는 id, name, amount, price이다.
5. "총 가격" 에 대한 정보는 계산을 통해 나온다.
6. 필요한 로직은 "추가" 와 "삭제"이다.
[추가 와 삭제 로직은 추후에 만들어보자]

 

1. 기본적인 createContext 틀 만들기

import { createContext, useReducer, useState } from "react";

const CartContext = createContext({
    itemInfo: [],
    totalAmount: 0,
    addItem: () => {},
    removeItem: () => {},
});

function cartReducerFn(state, action) {

}

export function CartContextProvider(props) {
    const [cartState, dispatchCartAction] = useReducer(cartReducerFn, {  
        itemInfo: [],
        totalAmount: 0,
    });

    function addItem() {}

    function removeItem() {}

    const context = {
        itemInfo: cartState.itemInfo,               //  "카트의 상태"의 itemInfo
        totalAmount: cartState.totalAmount,         //  "카트의 상태"의 totalAmount
        addItem: addItem,
        removeItem: removeItem,
    };

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

         * itemInfo 에는 id, name, amount, price 가 들어간다.

 

 

2. Provider 를 사용하여, 전체 앱을 감싼다.

        import { CartContextProvider } from "./store/Cart-Context";

            const root = ReactDOM.createRoot(document.getElementById("root"));
            root.render(
                <CartContextProvider>
                    <Layout>
                        <MainBackround />
                        <App />
                    </Layout>
                </CartContextProvider>
            );
            
   ==> index.js 에, ContextProvider 로 컴포넌트들을 감싸주어,
       createContext 로 만든 context가, "전역으로 공급" 되게 만들어준다.
       "공급된 함수"를 통해, 상향식 프로퍼티 이동으로, context의 값을 변화시키고
       변화시켜진 context들은 다시 전역으로 공급되어, 
       "모든 애플리케이션 전체" 에서 사용 가능한  context를 완성시킨다.

 

 

 

3. 첫번째로 필요한 "카트"버튼 옆의 숫자를 표시하기 위한, useContext를 사용한다.

   카트 옆의 숫자는 "총 amount" 를 의미한다.

   더하는 것은 Array.reduce() 를 사용한다.

            import CartContext from "../../../store/Cart-Context";

            function LayoutHeader() {
                const [showModal, setShowModal] = useState(false);
                const cartCtx = useContext(CartContext);

                const totalItemAmount = cartCtx.itemInfo.reduce((acc, item) => acc + item.amount,0);
                ...
            }

 

 

 

4. 페이로드를 이용해, 새 값을 추가하는 로직을 만든다.

   복잡한 로직을 사용해야 하기에, 가독성과, 함수 정리를 위한 useReducer Hook 을 사용한다.

   useState 를 사용하기에는, 컴포넌트 함수의 가독성이 매우 떨어질 것이다.

 

"추가" 에서 가장 중요한 것은, "어떻게 값을 받아오는가" 이다.

방법이야 여러가지이지만, useReducer 를 사용하는한, action 객체를 사용하는것이 가장 바람직하겠다.

 

 

일단 기본 useReducer 훅을 잡아 둔 후에

 import { createContext, useReducer, useState } from "react";

const CartContext = createContext({
    itemInfo: [],
    totalPrice: 0,
    addItem: () => {},
    removeItem: () => {},
});

function cartReducerFn(state, action) {
                        
}

export function CartContextProvider(props) {
   const [cartState, dispatchCartAction] = useReducer(cartReducerFn, {   // cartState 로 카트의 "상태"를 저장
       itemInfo: [],                   // 카트 "상태" 의 초기값 지정 (무얼 넣을거라는 안내도 되도록, 직접넣음)
       totalPrice: 0,                // "itemInfo" 에는, "id, name, amount, price" 가 들어갈것이다.     
   });

   function addItem() {}

   function removeItem() {}

   const context = {
      itemInfo: cartState.itemInfo,               //  "카트의 상태"의 itemInfo
      totalPrice: cartState.totalPrice,         //  "카트의 상태"의 totalAmount
      addItem: addItem,
      removeItem: removeItem,
   };

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

 

 

페이로드를 이용하여, "새 값을 추가하는 로직" 을 일단 완성시킨다.

Array.concat() 을 사용하여, "기존의 배열을 가지고 조작하지 않도록 한다."

function cartReducerFn(state, action) {

   // "state 는 현재상태", "action" 은 "dispatch 안에 들어있는 객체"들에 접근한다.
   // "페이로드" 로, "action 안에 type 뿐 아니라, item 객체를 넣어" "새로 넣을 값"을 추가한다.

    if (action.type == "ADD_ITEM") {
       const updatedItem = state.itemInfo.concat(action.item);         // 새 값은 item 객체이다.
       const updatedPrice = state.totalPrice + action.item.price * action.item.amount; 
       return {
         itemInfo: updatedItem,
         totalPrice: updatedPrice,
       };
      }
     if (action.type == "REMOVE_ITEM") {
        ...
     }
 }
                    
export function CartContextProvider(props) {
    const [cartState, dispatchCartAction] = useReducer(cartReducerFn, {         // 넣을 데이터의 초기값(안내)
       itemInfo: [],
       totalPrice: 0,
    });
                    
    function addItem(item) {
       dispatchCartAction({ type: "ADD_ITEM", item: item });   // 페이로드로, 새 값을 넣어준다.
    }				// item 객체 안에는 [id,name,amount,price] 가 들어있다.
                    
    function removeItem(id) {
        dispatchCartAction({ type: "REMOVE_ITEM", id: id });    // 페이로드로, 변경할 데이터에 접근한다.
    }

         ...
}

 

 

5. 가장 중요한, Cart에 담는 로직을 추가해준다.

 프로세스

1. cartState 는 "itemInfo" 와 "totalPrice" 를 담고있다.
2 "itemInfo" 에는 [ { item1...}, {item2...}, ] 의 배열 객체로 담겨있고
  item 들은 id,name,price,amount 로 구성되어있다.
  
3. 만약 같은 id가 "있다면", findIndex 로, 그 Index를 찾아, 고치고, 오버라이드한다.
4. 만약 같은 id가 "없다면", 새로 넣는 item 은 그대로 concat 으로 Array에 들어갈것이다.

5. 또한 같은 id가 "있다면", 변하는 것은 amount 뿐이므로, amount를 고친 후, 오버라이드한다.

6. totalPrice는, 현재 상태에서, 추가될때마다 추가된 price 를 받는다.
7. price는 추가 될때마다, 추가된 amount와 개당 price를 곱한 값이 된다.

 

 

이것을 토대로 reducerFunction의 type : ADD 로직을 만든다.

 function cartReducerFn(state, action) {                     // "state 는 현재상태", "action" 은 "dispatch 안에 들어있는 객체"들에 접근한다.
              
    if (action.type === "ADD_ITEM") {                       // "만약 같은 id가 있다면 그 id가 있는 index를 찾는다."
       const sameIdIndex = state.itemInfo.findIndex(
         (item) => item.id == action.item.id
        );
            
       const sameIdItem = state.itemInfo[sameIdIndex];         // "만약 INDEX 가 없다면, NULL 이 반환"
            
       let updatedItem;                                         // 업데이트된 하나
       let updatedItems;                                       // 업데이트된 전체
            
       if (sameIdItem) {           
          updatedItem = {                             // 만약 존재한다면, [amount 는, 기존 상태+추가상태] 로 오버라이드
            ...sameIdItem,
            amount: +sameIdItem.amount + +action.item.amount,
           };
          updatedItems = [...state.itemInfo];                     // 기존 최종 itemInfo 를 가져 온 후
          updatedItems[sameIdIndex] = updatedItem;                // 새로운 상태를 받은 index 만 오버라이드
        } else {
          updatedItems = state.itemInfo.concat(action.item);          // 만약 없다면, 기존처럼, updatedItem 은 그냥 받아온 그대로, 추가상태는 추가상태 그대로
        }
            
        const updatedPrice =
          state.totalPrice + action.item.price * action.item.amount;      // 새 값은 item 객체로 넣어준다.
            
        return {
           itemInfo: updatedItems,
           totalPrice: updatedPrice,
           };
        }

 

다음번에는 카트 내에서 조작하는 것과, 삭제 로직을 추가해보자

댓글