[React/Node] 마켓컬리 클론코딩 4 - 상세 페이지
- 제품 상세 페이지(상품 설명 이미지 부분 제외)
기존의 메인 페이지와는 별개의 페이지이기 때문에 <App.js> 파일을 수정하였다.
<App.js>
import './App.css';
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Header from './components/Header';
import Home from "./pages/Home";
import ItemDetail from './pages/ItemDetail';
const App = () => {
return(
<BrowserRouter>
<Header/>
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/itemDetail/:item" element={<ItemDetail/>}/>
</Routes>
</BrowserRouter>
);
}
export default App;
링크를 부여하고 해당 페이지로 이동할 수 있도록 하기 위해선 각 제품마다 고유 값을 가진 서로 다른 링크를 할당받아야한다. 그렇기에 "../:item"과 같은 동적 라우트 매개변수를 통해 각 제품에 맞는 페이지가 나올 수 있도록 구성하였다.
초기에는 element 대신 component를 사용했었는데, 분명 같은 역할을 하는 것으로 생각하고 사용하였는데 component를 사용하였을 때는 어떠한 화면도 출력되지 않았다. 제품 상세 페이지는 안나오지만 윗부분의 로고 등이 담김 헤더부분은 출력되는 것을 확인하고 element와 component의 차이가 이러한 결과를 만든게 아닐까 싶었다.
결과적으로는 element는 jsx 요소를 렌더링하고, component는 컴포넌트를 랜더링 할 때 사용된다는 차이가 있다. 간단히 동작의 차이만 정리하면, element는 반환값이 있어도 되고, component는 반환값이 없는 함수(?)를 사용한다는 것이다.
나는 이후 <ItemDetail.js>파일에서 상세 페이지에 맞는 문서를 반환해주어야 하기 때문에 여기에서는 element를 사용하는 것이 적합했다.
<ItemDetail.js>
import React, { useState } from "react";
import WeekItem from "../images/WeekItem";
import { useParams } from "react-router-dom";
import '../css/ItemDetail.css';
const ItemDetail = () => {
const { item } = useParams(); //동적 라우트 매개변수는 라우트 설정과 훅에서의 이름이 서로 일치해야함.
const [quantity, setQuantitiy] = useState(1);
return (
<article>
<div className="info_wrap">
<div className="info_img">
<img src={WeekItem[item - 1].src} />
</div>
<div className="info_text_wrap">
<div className="main_info">
<p className="title">{WeekItem[item - 1].title}</p>
<p className="disc">{WeekItem[item - 1].discount}%</p>
<p className="disc_price">{WeekItem[item - 1].disc_price}원</p>
<p className="price">{WeekItem[item - 1].price}원</p>
</div>
<div className="sub_info"> {/*추후에 유동적인 데이터 표시가 가능하게 수정필요*/}
<dl>
<dt>배송</dt>
<dd>샛별배송<h5>23시 전 주문시 내일 아침 7시 전 도착<br />(대구·부산·울산 샛별배송 운영시간 별도 확인)</h5></dd>
</dl>
<dl>
<dt>판매자</dt>
<dd>컬리</dd>
</dl>
<dl>
<dt>포장타입</dt>
<dd>상온(종이포장)<h5>택배배송은 에코포장이 스티로폼으로 대체됩니다.</h5></dd>
</dl>
<dl>
<dt>판매단위</dt>
<dd>1개</dd>
</dl>
<dl className="item_select">
<dt>상품선택</dt>
<dd>
{WeekItem[item - 1].title}
<Quantity quantity={quantity} setQuantitiy={setQuantitiy}/>
<p className="sel_discP">{WeekItem[item-1].disc_price}원</p>
<p className="sel_price">{WeekItem[item-1].price}원</p>
</dd>
</dl>
</div>
<div className="price_info">
총 상품금액: <h2>{WeekItem[item-1].disc_price}</h2><h3>원</h3>
</div>
<div className="add_Bt">
<button >♡</button>
<button className="add_cart">장바구니 담기</button>
</div>
</div>
</div>
</article>
)
}
function Quantity(props) {
const cal = (num) => {
if(num < 0){
if(props.quantity > 1){
props.setQuantitiy((prev) => prev + num);
}
} else props.setQuantitiy((prev) => prev + num);
}
return (
<div className="quan_bt">
<input type="button" value="–" onClick={()=>cal(-1)}/>
{props.quantity}
<input type="button" value="+" onClick={()=>cal(1)}/>
</div>
)
}
export default ItemDetail;
어찌보면 게시판의 읽기 기능과 비슷해서 크게 새로운 개념이 필요하지는 않았지만, 이번 프로젝트는 디자인을 신경쓰다보니 css적으로 많은 공부를 한 것같다.
일단, 제품 별로 페이지를 보여주어야 하기 때문에 기존에 이용한 동적 라우트 매개변수를 이용하는 것이 효율적이라고 판단했다. useParams 훅을 이용해 전달받은 매개변수 값을 저장하고 이를 아이템 번호로써 이용하도록 하였다. 다만, 이용하기 편하도록 매개변수 값을 처음에는 "id"로 저장하였더니 undefined 값이 저장되었는데, 이는 제대로 전달받고 있지 못하다는 것이다. 링크에서 동적 라우트 매개변수로 a라는 값을 설정하였다면, 해당 매개변수를 훅으로 전달받아 사용할 때에도 매개변수의 이름이 a로 동일하여야한다는 조건이 존재하였다. 전혀 다른 컴포넌트라 매개변수의 이름은 달라도 될 줄 알았는데...
여튼 나머지는 기존의 WeekItem.js의 데이터를 통해 작성하였고, 수량 변경 부분만 따로 컴포넌트로 만들어 생성하였다.
(슬라이드 만들 때는 몰랐는데, WeekItem.js의 파일 이름을 고치는 것이 좋아보인다.)
<ItemDetail.css>
article {
padding: 2% 15% 0 15%;
}
.info_wrap {
display: flex;
}
.info_wrap .info_img {
width: 40%;
padding-right: 5%;
}
.info_wrap .info_img img{
width: 100%;
height: 600px;
border-radius: 10px;
}
.info_wrap .info_text_wrap {
width: 55%;
height: 1000px;
padding: 20px 0;
}
.info_text_wrap .main_info {
.title { font-size: 23px; font-weight: 550; padding-bottom: 30px;}
.disc {
font-size: 25px; font-weight: 550;
color: orangered; display: inline-block;
}
.disc_price {
display: inline-block; padding-left: 10px;
font-size: 23px; font-weight: 550;
}
.price { color: gray; text-decoration: line-through; }
padding-bottom: 40px;
}
.info_text_wrap .sub_info {
color: rgb(11, 11, 11);
font-size: 14px;
padding-bottom: 60px;
dl {
border-top: 1px solid rgb(238, 238, 238);
padding: 20px 0;
display: flex;
dt { width: 25%; }
dd h5 { font-weight: 500; color: rgb(59, 59, 59); }
}
.item_select dd {
width: 100%;
font-size: 12px;
border: 1px solid rgb(238, 238, 238);
padding: 10px;
input {
border: none; background-color: transparent;
font-size: 18px;
}
.quan_bt {
font-weight: 550;
text-align: center;
margin-top: 20px;
padding: 5px 10px;
border: 1px solid rgb(238, 238, 238);
font-size: 15px;
width: 20%;
}
input[value="+"] {float: right;}
input[value="–"] {float: left;}
p { float: right; }
.sel_discP{ padding-left: 5px; font-weight: 550;}
.sel_price{ color: gray; text-decoration: line-through; }
}
}
.info_text_wrap .price_info {
padding-bottom: 60px;
font-size: 14px;
text-align: right;
h2 {
font-size: 30px;
display: inline-block;
padding-left: 5px;
}
h3 { display: inline-block; font-size: 19px; padding-left: 5px;}
}
.info_text_wrap .add_Bt button {
border: 1px solid rgb(216, 216, 216);
background-color: transparent;
font-size: 25px;
font-weight: 550;
color: rgb(111, 0, 128);
width: 10%;
height: 60px;
border-radius: 4px;
}
.info_text_wrap .add_Bt .add_cart {
margin-left: 5%;
width: 85%;
display: inline-block;
height: 60px;
background-color: rgb(111, 0, 128);
color: white;
}
전체적으로 웹 제작이 마무리되어가면 아무래도 css파일을 어떻게든 해야할 거 같다.
일단은 작동시키는게 먼저니 되는 대로 작성하였지만, 너무 더러워 보인다.
다른 분들이 한 것을 보면 js파일 안에 위치하도록 하기도 하고 여러 파일로 분할해서 적용시키기도 하던데, 이 부분도 공부가 필요할 것 같다.
https://memo-code.tistory.com/38
마켓컬리 클론코딩 5 - 리뷰 및 문의 페이지
리뷰 페이지 문의 페이지 import React from "react"; import { review } from "../images/imgidx"; import WeekItem from "../images/WeekItem"; import "../css/Review.css"; const Review = (props) => { let item = props.item - 1; return ( 상품 후기 {re
memo-code.tistory.com