일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- @tailwind components
- interceptor routes
- sever components
- 3진법 뒤집기
- static pages
- client components
- 클로저
- 프로그래머스
- @tailwind utility
- @tailwind
- sever action
- revalidatetag
- dynamic pages
- 자바스크립트
- unstable_nostore
- server components
- iron-session
- CSS
- image component
- RECOIL
- commit phase
- js
- revalidatepath
- SSR
- supabase realtime
- createbrowserrouter
- 타입스크립트
- @tailwind base
- 리액트
- render phase
- Today
- Total
개발하는 너구리
구멍마켓 프로젝트 본문

팀프로젝트 프론트 GitHub 레퍼지토리
GitHub - wecode-bootcamp-korea/justcode-7-1st-goomeongmarket-front
Contribute to wecode-bootcamp-korea/justcode-7-1st-goomeongmarket-front development by creating an account on GitHub.
github.com
Project Overview
저스트코드에서의 1차 프로젝트가 끝났다. 내가 제안했던 사이트인 마켓컬리가 감사하게도 모델링하고자 하는 프로젝트의 사이트로 지정되었다.
마켓컬리를 제안했던 이유는 e-commerce를 경험해보고 싶었기 때문이다. e-commerce는 장바구니 기능, 찜(관심상품) 기능, 회원가입, 로그인, 제품 상세페이지, 카테고리 필터기능 등 다양한 기능들의 총집합이기 때문이다. 물론 2주라는 짧은 프로젝트 기간때문에 모든 기능들은 구현하진 못했지만, 모든걸 올인할만큼 최선을 다했던 프로젝트였다.
React를 사용하여 e-commerce 사이트 제작
- 목데이터가 아닌 백엔드 서버에서 데이터를 fetch 해오는 첫 프로젝트
- 리액트 컴포넌트를 공유하여 효율성을 높이는 과정
- 마켓컬리 사이트를 연구해가며 html 구조와 scss 의 속성들의 사용법을 명확히 알게된 계기
작업 기간
2022.10.31 ~ 2022.11.11
기술 스택
프론트엔드 4명
- HTML/CSS
- SASS
- JavaScript
- React
백엔드 3명
- Node.js
- MySQL
구멍마켓 프로젝트 전체 시연 영상
주요 구현 사항
😻 표시는 내가 기여한 기능
- 상세페이지 레이아웃 및 css 스타일링 구현 😻
- quertString url 를 사용한 동적라우팅 상세페이지 연결 구현 😻
- 상세페이지 내 찜버튼 UI기능 구현 😻
- 상세페이지 내 장바구기 담기 버튼 UI기능 구현 😻
- 상세페이지 내 브라우저 하단 상품선택 슬라이드 UI기능 구현 😻
- top 버튼 구현 😻
- 상세페이지 내 자세히보기 / 닫기 기능 구현 😻
- 상세페이지 내 후기(reivew) 컴포넌트 작성 및 기능 구현 😻
상세페이지 전반적인 레이아웃 & 스타일링
상세페이지는 최대한 마켈컬리와 비슷하게 표현하고자 css에 공을 들였다...이게 제일 힘든 과정인건 왜였을까..

상세페이지 내 찜 버튼, 장바구니 담기 기능

찜버튼
찜버튼 활성화/비활성화 시 해당 상품의 product_id값을 POST / DELETE 방식으로 백엔드 서버에 전송
//ProductDetailedPage.js
const [isWishAdd, setIsWishAdd] = useState(false);
const wishAddHandler = () => {
setIsWishAdd(!isWishAdd);
};
const wishCountHandler = () => {
wishAddHandler(); //wishCountHandler 함수 실행시 isWishAdd 스테이트값 변경(활성/비활성)
if (!isWishAdd) {
fetch('http://localhost:8000/like/addlike', {
method: 'POST',
headers: {
'Content-type': 'application/json',
token: localStorage.getItem('token'),
},
body: JSON.stringify({
product_id: params.id,
}),
});
alert('찜 목록에 추가되었습니다');
} else if (isWishAdd) {
fetch('http://localhost:8000/like/removelike', {
method: 'DELETE',
headers: {
'Content-type': 'application/json',
token: localStorage.getItem('token'),
},
body: JSON.stringify({
product_id: params.id,
}),
});
alert('찜 목록에서 삭제되었습니다');
}
};
../
<button className="heartButton" onClick={wishCountHandler}>
//찜버튼 클릭시 wishCountHandler함수 실행
찜하기버튼의 기능은 2가지이다.
버튼 활성화 시 해당 상품의 product_id 값을 POST방식으로 백엔드 서버로 보내주기
버튼 비활성화 시 product_id 값을 DELETE방식으로 서버에 보내주기
이 방식을 boolean 타입의 state를 활용했다. false = 버튼 비활성화 / true = 버튼 활성화
즉, isWishAdd라는 스테이트의 값(true/false)에 따라 다른 조건문이 실행하게끔 코드를 작성해 찜기능 버튼의 UI를 구현
장바구니 담기 버튼
장바구니 버튼 클릭 시 해당 상품의 product_id값, 수량(put_quantitiy)을 POST 방식으로 백엔드 서버에 전송
버튼 기능 동작은 찜하기버튼과 같은 로직으로 작성하였다
const [isCartAdd, setIsCartAdd] = useState(false);
const cartCountHandler = () => {
cartAddHandler();
if (!isCartAdd) {
fetch('http://localhost:8000/cart/update', {
method: 'POST',
headers: {
'Content-type': 'application/json',
token: localStorage.getItem('token'),
},
body: JSON.stringify({
product_id: params.id,
put_quantity: number,
}),
});
}
alert('장바구니에 추가되었습니다');
};
하단 '상품 선택' 팝업 컴포넌트

//ProductDetailedPage.js
const [showSelect, setShowSelect] = useState(false);
const handleShowButton = () => {
if (window.scrollY > 650) {
setShowButton(true);
setShowSelect(true);
} else {
setShowButton(false);
setShowSelect(false);
}
};
//일정량 이상의 스크롤다운 되면 상품선택버튼이 화면에 나타나도록 로직 구현
../
{showSelect && (
<SelectDown
title={change.title}
number={number}
price={change.price}
increase={increaseNumber}
decrease={decreaseNumber}
converPrice={converPrice}
isWishAdd={isWishAdd}
wishCountHandler={wishCountHandler}
cartCountHandler={cartCountHandler}
/>
)};
//props를 활용한 프로퍼티값 전송
//SelectDown.js
const [selectClick, setSelectClick] = useState(false);
function SelectDown({
title,
number,
price,
decrease,
increase,
converPrice,
isWishAdd,
wishCountHandler,
cartCountHandler, //구조분해할당으로 props를 전달받아 사용
}) {
const [selectClick, setSelectClick] = useState(false);
return (
<div className="selectContainer">
<div className="buttonWrap">
<button
className="selectUp"
onClick={() => setSelectClick(!selectClick)}
>
<span>상품 선택</span>
<img src={'/icons/selectUp.png'} alt="업" />
</button>
{selectClick && ( //조건부 렌더링을 활용한 상품선택버튼 클릭후 SelectUp컴포넌트 활성화
<SelectUp
title={title}
number={number}
price={price}
increase={increase}
decrease={decrease}
converPrice={converPrice}
isWishAdd={isWishAdd}
wishCountHandler={wishCountHandler}
cartCountHandler={cartCountHandler}
/>
)}
</div>
</div>
);
}
//SelectUp.js
function SelectUp({
title,
number,
price,
increase,
decrease,
converPrice,
isWishAdd,
wishCountHandler,
cartCountHandler,
}) {
return (
../
<div className="selectButton">
<button className="selectLike" onClick={wishCountHandler}> //찜버튼 클릭시 활성/비활성
<span>
<img
src={
isWishAdd //isWishAdd 스테이트값(true(활성)/false(비활성))따라 아이콘 컬러 변경
? '/icons/redheart.png'
: '/icons/purpleHeart.png'
}
width="32px"
alt="찜버튼"
/>
</span>
</button>
<div className="selectCartWrap">
<button className="selectCart" onClick={cartCountHandler}> //장바구니버튼
<span>장바구니 담기</span>
</button>
</div>
하단 '상품선택' 팝업 컴포넌트에도 동일하게 찜기능, 장바구니담기 기능을 똑같이 구현했다. 이 과정에서 props를 적극 활용했다.
Project Review
프론트-백의 원할한 소통이 정말 중요!!!
프론트엔드도, 백엔드도 각자의 분야만을 공부하다가 함께하는 첫 프로젝트였다. 때문에 프로젝트를 시작하면서 데이터가 어떤 형식으로 넘어오는지, 키값은 어떻게 되는지, POST , DELETE 시 어떤 값을 전달해줘야 하는지 등 서로 소통하는것이 정말로 중요하다는 것을 뼈저리게 느꼈다!!
백엔드 서버로부터 데이터를 받아와서 렌더링 시키려하는데 오류가 발생한것이다... 난 내 로직이 잘못되었나? fetch 방법이 잘못되었나?
이래저래 잘못된 것을 찾아 2시간 넘게 방황하던중 드디어 원인을 찾아냈다. 원인은 너무나 간단했다. 백엔드와의 소통이 부족했다는걸 2시간이란 시간이 더 강하게 느끼게 해주었다. 데이터로 넘어오는 key값이 변경되었다는 걸 내가 모르고있었었다는게 원인이었다...
아무리 늦은 시간이어도 데이터 전송과정을 맞춰보자는 요구에 언제든지 상냥한 말투로 흔쾌히 맞이해준 특히 백엔드 성희님께 너무나도 감사하다고 말씀드리고싶다.. 내가 너무 귀찮게해드렸어... 감사합니다!
앞으론 이런 불필요한 시간 낭비를 줄이고자 미리 사전에 데이터 key값, POST요청시 필요한 요구사항등은 미리미리 소통해 알아두는것이 양쪽모두에게 좋다는걸 알았다. 어떻게보면 기본중의 기본이겠지만, 내가 하고있는 작업에만 너무 몰두해 소통의 중요성을 잊기도했다..
다음 프로젝트에는 꼭 소통을 자주하려고 노력하겠다!!
개선하고 싶은 점
기능구현에 치중하여 코드 리팩토링에 신경쓰지 못했다.
2주가 채 안되는 짧은 시간 동안 최대한 많은 기능구현을 해내야한다는데만 몰두했다. 그래서 코드의 효율성에는 상대적으로 에너지를 쓰지 못했다. 분명히 코드를 효율적으로 줄이면서 오는 나름의 재미가 있는데...
첫프로젝트라서 그런지 시작부터 욕심이 많았다. 특히 css...분명 중복적용시켜 불필요한 속성들이 꽤나 많을거라고 생각한다
비록 하나의 scss파일로 상세페이지 컴포넌트 전체를 담당한다해도 1200줄이 넘는다. 리팩토링을 다시해보면 1000줄 안쪽으로 줄어들건데... 수많은 className에 솔직히 리팩토링하기 지레 겁부터 난것도 맞지...
팀 프로젝트 후기
짧은 시간이었지만 팀원들과 함께 좋은 결과물을 낼 수 있었고, 그 과정이 비록 힘들었지만 반 미친상태(?)로 즐거웠다 ㅋㅋㅋ
매일 새벽늦게까지 디스코드에 접속해 소통하던 우리팀원들 모두 너무 수고많았습니다!!
특히 발표 하루 전날 최종 머지한 후 상세페이지 렌더링이 안되 몇시간째 원인을 찾던게 데이터로 넘어오는 JSON파일은 문자열형식이라 숫자와 비교시 일치연산자(===) 대신 동등연산자(==)를 써야한다는 제 불찰이었죠 ㅠ 무튼 너무너무 수고으셨습니다!!