이번 시간에는 장바구니 정보를 반환하겠습니다
이제 새로운 장바구니(Cart) 컴포넌트를 만들도록 하겠습니다
이렇게 하면
이 다른 컴포넌트 파일들을 각각의 하위 폴더에
정리할 수 있습니다
예를 들어
Meals라는 하위 폴더를 만들어서 메뉴와 관련된 두 항목을 넣거나
Cart라는 하위 폴더를 만들어서 장바구니 관련 파일 및 컴포넌트를
정리할 수 있습니다
이렇게 작업하는 것도 고려해 보시기 바랍니다
여기에서는 아직
그렇게 컴포넌트 수가 많지 않아 그렇게 하지 않지만
이 폴더 구조를 만들어서 작업하면
더 직관적인 방식으로 작업할 수 있게 됩니다
이제 Cart.jsx에서
Cart라는 함수를 내보내겠습니다
이 함수는 화면에
장바구니 데이터를 띄우게 됩니다
이 컴포넌트 함수에서는
새 모달 컴포넌트를 반환하겠습니다
그렇게 하면 모든 장바구니 데이터가
모달을 통해 화면에 보여지게 됩니다
먼저는
장바구니라고 하는 제목을 반환하겠습니다
장바구니를 위해 설정한 형식도 적용하겠습니다
이것을 위해 모달을 변환 가능한 형태로 만들고
className을 받을 수 있도록 작업했습니다
이렇게 하면 여기서 className을 설정해서
장바구니의 값으로 설정할 수 있게 됩니다
index.css에서 찾을 수 있는
요소에 형식을 더하기 위한 클래스 이름은
여기에서는 장바구니 데이터에 적용됩니다
이 제목 아래쪽에
장바구니 항목을 나열하겠습니다
그리고 이 항목은 CartContext에
저장되어 있습니다
이 장바구니 컴포넌트 또한 컨텍스트에 접근이 필요한데
그렇게 해야 이 항목에 접근할 수 있기 떄문입니다
그러므로 useContext를 호출하여 리액트로부터 가져와서
컨텍스트 사용을 위해 CartContext를 전달하고
이 파일 형식을 가져온 내용에 더해서
이런 식으로 cartCtx를 받아옵니다
이제 장바구니 컨텍스트에 접근이 생겼으니
장바구니의 항목을 .map 할 수 있습니다
이 ol에서
cartCtx.items에 접근하여 항목을 .map할 수 있는데
각 item마다 장바구니 항목을 반환하겠습니다
현재로서는 일단 기본적인
리스트 항목으로 시작하겠습니다
나중에 좀더 복잡해지겠지만 일단은
item.id라는 키(key)를 가진 목록 항목입니다
이 Cart 항목들은 mealItems에 기반한 것이고
각 메뉴에도 id가 있습니다
그러므로 Cart 항목에서 메뉴에도 접근이 가능합니다
여기에서는 item.name을 반환하고
이 플레이스홀더(placeholder)가
대시(-)로 item.quantity와 분리되어 있는데
이 부분은 조금 있다가 수정하겠습니다
이 ul 아래에
총 금액을 반환하겠습니다
스타일링의 이유로
이 p요소에 className을 cart-total로 하고
총 금액을 계산하는 데에
헬퍼(helper) 상수인 cartTotal의 도움을 받아
cartCtx.items와 reduce 함수를 사용하겠습니다
이것은 Cart 항목의 총 수량을 파생시키는 것이 아니라
총 금액을 파생시킵니다
이 로직은 이전에 저희가 총 수량을 위해 만들었던 것과
매우 유사합니다
먼저는 0값으로 시작해서
여기에 총 금액(totalPrice)와 item 데이터를 넣고
반환하려는 값은 총 금액(totalPrice)에
총 수량(item.quantity)과
각 금액(item.price)을 곱한 것을 더한 것입니다
이렇게 하면 장바구니의 총 금액이 나옵니다
이 장바구니는 이전에 작업했었던 아래쪽의
curencyFormatter의 반환값이 됩니다
이제 이 currencyFormatter를 util에서
가져오기 위해
formatting.js 파일을 불러오겠습니다
이 문단에서 최종 금액 형식을 이렇게 설정하겠습니다
그리고 총 금액 아래에는 문단을 하나 더해서
modal-actions라는 클래스 이름을 설정해서
버튼 두개를 반환하겠습니다
버튼 하나는 모달을 닫는데 쓰고 다른 하나는 그 모달을 닫되
다음 단계인 결제창으로 넘어가도록 하겠습니다
이전에 만들어 본 적이 있는 버튼 컴포넌트를
한 번 더 만들어겠습니다
이 컴포넌트는 Button.jsx에서 가져옵니다
첫 버튼은 textOnly 형식으로 문자만 있으며
'닫기(Close)'라고 되어 있습니다
두 번째 버튼은 다른 형식으로
'결제하기(Go to Checkout)'이라고 되어 있습니다
지금으로서는 이 버튼은 작동하지 않지만
조금 있다가 이 내용을 작업해 보겠습니다
지금은 이 모달을 띄울 방법에 대해 작업하겠습니다
Cart 컴포넌트를
어디에서 어떻게 사용하든지 이 open 속성, 즉
모달 컴포넌트가 대화상자를 띄우기 위해 필요한 그 속성이
설정되지 않고 있습니다
물론 이것을 하드코딩 (고정된 값의 코딩)으로 true로 만들 수 있지만
이렇게 하면 창을 닫는 작업이 어려워집니다
추가로 이렇게 되면 모달이 계속 띄워져 있어야 하는데
그 결과는 여기서 원하는 바가 아닙니다
이 모달은 사용자가 이 홈페이지에서
이 버튼을 누를 때만 떠야 합니다
이 문제를 어떻게 해결해야 할까요?
여기에도 사용할 수 있는 다양한 방법이 있습니다
저는 여기에 컨텍스트를 하나 더해서
앱의 다양한 위치에서 제어할 수 있는 컨텍스트로서
장바구니 데이터를 보여주는 부분을 담당하도록 하겠습니다
이 store 폴더 안에
컨텍스트 파일을 하나 더하겠습니다
이름은 UserProgressContext로 하겠습니다
장바구니를 확인하고 결제창으로 넘어가는 과정의
각각의 단계가 결국에는 사용자가 웹사이트에서 경험하는
연속적인 여정이라고 볼 수 있기 때문입니다
그래서 이름이 사용자의 과정에 대한 컨텍스트(UserProgressContext) 입니다
CartContext 파일과 동일하게
일단은 createContext를 리액트에서 가져오고
그 다음에 createContext를 호출해서
이 UserProgressContext를 만들겠습니다
그리고 가상의 객체 하나를 가져오겠습니다
이 객체는 progress라는 속성을 가지고 있어서
초기에는 빈 문자열이지만 나중에는
사용자가 장바구니(cart)를 보고 있거나
결제 창(checkout)으로 넘어갈 수도 있습니다
물론 이 과정의 변화를 위한
함수가 필요한데
예를 들면 장바구니를 보이는 showCart 함수
다시 닫는 hideCart 함수
결제창으로 넘어가는 showCheckout 함수 그리고
닫는 hideCheckout 함수입니다
여기는 콜론으로 수정하겠습니다
이 UserProgressContext를 여기에 기본값으로 내보내고
이 함수를 더하겠습니다
바로 UserProgressContextProvider이고 이 함수도 내보내겠습니다
여기에서는 특수 자식 속성을 사용해서
UserProgressContextProvider를
감싸는 데에 쓰겠습니다
감싸는 내용은
이 커스텀 ProgressContextProvider 컴포넌트의 시작과 종료 태그 사이로
전달되는 내용입니다
이제 여기에서 상태 제어가 가능합니다
이 작업은 reducer를 사용해도 되지만
새로운 방식으로 작업하도록
그리고 이 상태가 모두 단순한 형태이므로
표준 useState 훅을 사용하겠습니다
리액트에서 가져와야 하는 훅인데
기본 상태는 비어 있습니다
즉 화면에 장바구니든 결제 화면이든
모달을 띄우지 않겠다는 뜻입니다
이름은 userProgress와 setUserProgress로 하겠습니다
이 상태는 이 네 개의 함수 중 하나가
호출될 때 실행됩니다
그러므로 여기에 showCart 함수를 더해서
UserProgress를 장바구니(cart)에 설정하겠습니다
그 다음에는 hideCart 함수에서
UserProgress를 빈 문자열로 되돌리겠습니다
그 다음 함수는 showCheckout입니다
여기에서는 UserProgress가 결제(checkout)으로 설정됩니다
그리고 마지막 함수는
hideCheckout이고 작성하는 방식은
이전과 같습니다
UserProgress를 설정하는 내용은 동일하게
빈 문자열입니다
이제 여기 있는 네 개의 함수과 값들을
모두 한데 모아서
UserProgresCtx 객체에 넣겠습니다
여기에는 이 progress 속성이
UserProgress 속성에 대응합니다
그리고 여기에서는 showCart 함수와
hideCart 함수, showCheckout 함수
그리고 hideCheckout 함수가 있습니다
여기서 정의한 이 모든 함수는
윗쪽의 임시 초기 값에도 있습니다
이제 이 userProgressContext를
UserProgressContextProvider 컴포넌트로 설정했습니다
이제 이 커스텀 provider 컴포넌트로
장바구니 등과 상호작용이 가능한 모든 다른 컴포넌트를
감쌀 수 있습니다
그렇게 되면 앱 컴포넌트로 돌아와서
CartContextProvider를 감싸거나
이 안쪽으로 들어와도 됩니다만
헤더와 음식 메뉴 사이를 감싸야 합니다
저는 CartContextProvider를 감싸고
UserProgressContextProvider도 이렇게
감싸도록 하겠습니다
그리고 이렇게 가져와서
이 컨텍스트를 사용해서 장바구니를
열거나 보여줄 수도 있고
장바구니가 보여야 하는지에 대한
정보를 사용할 수 있습니다
예를 들어서 이제는
헤더 컴포넌트로 가서
이 버튼을 클릭하면 장바구니를 열 수 있는 것과 동시에
다른 컨텍스트 사용이 가능합니다
원하는 만큼 컨텍스트 값 설정이 가능하기 때문입니다
저는 여기에서 UserProgressContext를 사용하겠습니다
이제 UserProgressCtx 값이 있으니
이제 헤더 컴포넌트 함수 안에서
handleShowCart라는 함수를 더하겠습니다
이 함수에서는
UserProgressCtx에 접근해서 showCart를 호출합니다
이 handleShowCart 함수는 이 버튼에
연결되어 있어야 하는데
onClick 속성을 추가해서 handleShowCart를 지정해 주면 됩니다
이제 해야 하는 작업은
이 장바구니 컴포넌트가 보이도록 하는 것입니다
장바구니 컴포넌트로 넘어와서
여기에서도 동일하게
UserProgressContext를 사용하겠습니다
그래서 여기에서는 userProgressCtx 값도 있습니다
이 안에는 이제 progress 속성이 있는데
이제 보니까
잘못된 값이 전달되고 있습니다
이 내용이 userProgressCtx여야 맞습니다
이 값이 이 값 속성으로서 전달되고 있기 때문입니다
그런데 여기에는 progress 속성이 있고
이 progress 속성이 바로 장바구니를
보일지 말지를 결정합니다
그러므로 모달에 open을 설정해서
userProgressCtx.progress를 cart와 대응시키겠습니다
만약 cart에 대응한다면
cart데이터가 있는 모달을 띄웁니다
즉 open이 true의 경우이기도 합니다
다른 경우는 false가 됩니다
이제 이 작업이 끝났으니
이 커스텀 장바구니 컴포넌트가
이 앱, 혹은 컴포넌트 트리에 속해 있도록 하겠습니다
이 내용도 앱 컴포넌트에
추가하면 될 것 같습니다
위치는 크게 중요하지 않습니다
이렇게 함으로써 이 컴포넌트가
그 컴포넌트가 수행할 바를 하도록 하는 것입니다
이제 새로고침을 한 후에 장바구니를 눌러 보면
이렇게 모달이 뜨게 됩니다
아직 장바구니 데이터는 없기 때문데
다시 닫고, 더 엄밀히는 새로고침을 하겠습니다
아직 모달 창을 닫는 기능이 제대로 없기 때문입니다
그래서 다시 새로고침을 해서 항목을 좀 담겠습니다
마르게리타 피자를 하나 더 담겠습니다
이제 장바구니로 가 보면
여기에 제가 담은 항목과 데이터가 있고
총 금액도 있습니다
모두 작업이 잘 되었습니다
이제 작업할 내용은
이 버튼이 모달을 닫도록 하는 것입니다
나중에는 이 버튼을 통해서
결제창으로도 넘어가야 합니다
그리고 이 항목의 형식도 조금 변경하겠습니다
일단은 이 버튼으로
이 모달을 닫을 수 있게 하겠습니다
이 작업을 위해서는 일단
장바구니 컴포넌트로 가서 버튼을 살펴보겠스빈다
결국은 hideCart 함수를 실행시켜야 하는데
이 함수는 userProgressCtx에 있습니다
그러므로 여기 함수를 하나 더하겠습니다
이름은 handleCloseCart로 하겠습니다
그리고 여기에서 UserProgressCtx 객체에 접근해서
hideCart를 호출하겠습니다
이렇게 하면 progress 값이 변하므로
결국에는 이 open 속성값이
모달을 지우게 됩니다
이제 이 handleCloseCart 함수를 버튼에 연결하겠습니다
일단은 두 버튼 모두와 연결시키겠습니다
닫기 버튼의 onClick 속성을 통해 기능을 더하고
결제하기 버튼에도 동일하게 하겠습니다
이대로 저장한 후에 확인해 보시면 여전히
작동하지 않습니다
새로고침 후 장바구니를 열어서 닫기나 결제하기를 눌러봐도
아무것도 작동하지 않습니다
버튼이 작동하지 않는 이유는 우리가
여기 있는 progress 속성을 변경함으로써
이 open 속성값도 변경하기 때문입니다
그런데 모달 컴포넌트에서 확인하는 것은
open이 true일 경우에 대화상자를 보여주는 것뿐이고
false의 경우에는 설정된 것이 없습니다
물론 이 effect 함수가
open이 변경되면 재실행되지만
false인 경우에 해당하는 로직이 없습니다
그러므로 이것이 재실행될 때 대화상자를 닫기 위해서는
cleanup 함수를 사용할 수 있습니다
이미 배우셨듯이 이 함수가 실행되는 시점은
이 effect 함수가 재실행되기 직전입니다
즉 이 open 속성값이 변할 때입니다
여기에서 dialog.current.close를 호출할 수 있지만
여기에서는 조금 다른 접근방식을
추천드립니다
추천드리는 방식은 이 참조(ref)값을
이 임시 상수에 저장하고
이 상수를 effect 함수에서 사용하는 것입니다
이렇게 if문과 cleanup 함수에도 사용했습니다
이 방식을 더 추천드리는 이유는
이 cleanup 함수는
시점상으로는 effect 함수보다
더 나중에 최초로 실행됩니다
그 이유는 cleanup 함수는 이 값이 미래에 변하는 때에만
실행되기 때문입니다
그러므로 이론적으로는 이 참조에 저장된 값이
그 사이에 변할 수 있습니다
여기에서는 그런 경우는 아닙니다
이 경우 늘 이 대화상자를 참조할 것입니다
하지만 이론적으로는 다른 값에 지정될 수도 있습니다
이 함수 실행 사이 어딘가에 지정될 수 있으므로
추천드리는 방식은 effect 함수가 실행될 때
이 참조값을 지정해 놓는 것입니다
그렇게 하면 동일한 값이
effect 함수에 해당하는 cleanup 함수에서도 사용됩니다
물론 이 방식이 필수적이지는 않지만
이 패턴을 추천드리기 때문에
실습으로 보여드렸습니다
이대로 저장하고 새로고침을 하면
이제 이 버튼이 작동을 해서
이 모달을 닫게 됩니다
그 후에 다시 여는 것도 작동이 되고
콘솔에서도 에러는 없습니다
즉 의도한 대로 잘 작동하고 있습니다
다음 시간에는 이 장바구니 항목의 형식을 수정하고
그 다음으로는 이 버튼을 통해
결제창으로 가도록 작업하겠습니다