대장정의 1차 프로젝트가 끝이났따✌️
다사다난하고 우당탕탕이었지만 좋은 팀원들과 함께 기획부터 회고까지 무사히 끝마칠 수 있었다 :)
우리 팀원 분위기가 너무 좋았다.... 진짜 여기서 2달전에 만났던 사람들 맞나 싶을정도로 프로젝트 하는동안 같이 울고 웃었던 기억들이 많다..!
나의 리드하에 팀원들의 코로나 격리로 원격으로 소통하고 애자일 스크럼에 맞게 1주단위의 Sprint로 팀 프로젝트를 진행했다.
Notion을 통해 daily standup metting기록과 Plan을 계획했고 Trello를 통해 전반적인 스프린트 개발일정을 공유하여 진척도를 조절하였다.
Scrum Process
애자일방법론
에 따라 Weekly Sprint로 총 2번의 Sprint를 진행하였다.
Planning Meeting
하나의 Sprint가 시작되는 매주 월요일마다 Trello를 사용하여 해당 Sprint때의 목표를 설정하였다.
Daily Standup Meeting
아침 10시마다 전날의 개발진척도와 오늘의 목표, blocker들을 공유하였다.
Retrospective Meeting
최종발표 날 두번의 Sprint동안의 KPT회고를 통한 회고를 진행하였다.
Gitbook
gitbook을 통해 백엔드와의 소통도 원만하게 진행되었다.
백엔드, 프론트엔드 페이지 담당자끼리 API Data 형식이나 무엇을 넘겨줄건지 고민하면서 미리 협의해두었다.
그럼에도 중간에 필요하거나 수정할 부분이 생기면 그때그때 소통하여 조율하여 개발을 진행하면서 큰 변화가 없이 진행할 수 있었다.
리스트페이지에서 처음에는 브랜드를 클릭했을 경우 Brand 컴포넌트를 return하게끔 switch case로 구현했었다. 멘토님의 피드백으로 객체를 사용해서 클릭한 탭의 id를 저장해서 해당하는 id의 content로 등록되어있는 컴포넌트를 불러오게끔 조건부 렌더링을 리팩토링 하였다.
👇 ProductList.js
const TABS = [
{
id: 0,
title: '브랜드',
content: <Brand />,
},
{
id: 1,
title: '가격',
content: <Price />,
},
{
id: 2,
title: '사이즈',
content: <Size />,
},
];
<div className="left-filter">
<ul className="filter-list">
{TABS.map(filter => {
const isCurrent = currentTab === filter.id;
return (
<li key={filter.id}>
<button
onClick={() =>
setCurrentTab(isCurrent ? '' : filter.id)
}
className={isCurrent ? 'current' : ''}
>
{filter.title}
</button>
</li>
);
})}
</ul>
</div>
{TABS.find(({ id }) => id === currentTab)?.content}
TAB
객체를 활용하여 id, content를 매칭하여 조건부 렌더링 구현map
을 돌려title
을 불러와 필터링 버튼을 생성하고 click시 현재 tab의 id값을 설정한다.- 현재 tab의 id를
TAB
객체에서 찾아 해당하는 id의 content인 컴포넌트를 불러온다.옵셔널체이닝
을 통해TAB
객체에서 현재 tab의 id가 없는 경우, 즉 tab이 다 닫혀있는 경우 content를 불러올 수 없기 때문에 예외처리를 해주었다.
** 현재 열려있는 tab을 한번 더 click시 현재 tab의 id와TAB
객체의 id가 같은 지 판별하여 현재 tab의 id를 reset하여 toggle기능 구현
브랜드, 가격, 사이즈의 정보를 state에 저장하였더니 새로고침이나 재접속 시 체크한 필터값이 날아가는 이슈가 발생했다.
검색 결과 상태로 저장하지 않고 url에 저장하는 querystring으로 리팩토링 하였다 !
👇 Brand.js
const [searchParams, setSearchParams] = useSearchParams();
//TODO: 브랜드 검색기능
const searchBrand = e => {
const filterBrand = BRAND.filter(brand =>
brand.name.includes(e.target.value)
);
setFilterList(filterBrand);
};
//TODO: querystring 생성
const prevQuery = searchParams.getAll('brandId');
const handleCheckbox = e => {
const { checked, value } = e.target;
if (checked) {
searchParams.append('brandId', value);
setSearchParams(searchParams);
} else {
searchParams.delete('brandId');
prevQuery
.filter(query => query !== value)
.forEach(query => searchParams.append('brandId', query));
setSearchParams(searchParams);
}
};
- checkbox에 check가 되었으면 searchparams에 append()를 통해 추가한다.
- check가 해제되었으면 해당 param key의 value들을 모두 삭제하고 이전에 모든 value들을 담아놓은
prevQuery
변수에서 e.target.value인 것들을 제외한 value들을 다시 searchparams에 담는다.
상세페이지를 하나씩 다 만들어야하나 고민하다가 동적라우팅을 알게 되었다!
👇 Product.js
<li>
<Link to={`/product-detail/${id}`}>
<img src={imgurl} alt="상품이미지" />
<div className="like" />
<div className="info">
<span className="brand">{brand}</span>
<span className="name">{name}</span>
<span className="price">{Number(price).toLocaleString()}</span>
<span className="heart">
<i className="fa-regular fa-heart" />
<em>999+</em>
</span>
</div>
</Link>
</li>
👇 Router.js
<BrowserRouter>
<Nav />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/product-detail/:productId" element={<ProductDetail />} />
<Route path="/product-list" element={<ProductList />} />
</Routes>
<Footer />
</BrowserRouter>
Router.js
의 path prop에:productId
로 이름을 지정한다.- 상품리스트페이지에서 어떠한 품목을 선택해도 상품 상세페이지로 이동하게된다.
💡 더 자세한 코드들이 궁금하시다면?
첫 프로젝트였던만큼 부족한 점이 많을 수 밖에 없었던 것 같다.
기획의 부실
프론트엔드끼리의 부실
백엔드끼리의 부실
시간의 부실 !!!!
역시 데드라인은 죽음뿐...버릴건 버리고 일정관리의 중요성을 느꼈다..
Trello 티켓을 세분화 하여 진행하기
코드를 지울 줄 아는 개발자가 되자..!
이번에 리팩토링을 하면서 다 지우고 처음부터 다시 짠 코드도 있었고 부분적으로 다시 수정한 코드도 있었다. 내가 짠 코드가 정답이 아니다. 물론 코드에 정답은 없다고는 하지만 내가 짠 코드에 고집을 부려서는 안된다.
리팩토링이던 협업 내에 컨벤션이 수정되었던 내 코드에 집착하지말고 과감히 지울 줄 아는 개발자가 되어야겠다고 생각했다.