프로젝트 종료 후, 직접 구현한 기능은 🚩, 팀원이 구현한 기능은 ➕
위코드 부트캠프 시작 전, 개인적으로 몇 가지 토이 프로젝트들을 진행하면서 JavaScript는 익숙한 편이었다. React도 functional component의 개념까지는 이해하고 들어 왔는데, 1차는 class component로 진행된 다고 하여 완전 생무지에서 시작된 프로젝트였다.
토이 프로젝트를 하며 무언가를 직접 만들어보면 흐름과 개념이 잡힌다는 것을 깨달았었는데, 이번 팀 프로젝트를 통해 React class component를 꼭 나의 것으로 만들고 싶었다.
이번 팀 프로젝트를 통해서 Frontend 혼자서는 구현하기 어려운 장바구니 기능을 꼭 해보고 싶었다.
Backend와 어떤 식으로 통신이 되는지, 어떤 순서로 제품이 장바구니에 담기고, 수정되고, 삭제되는 지 배우고 싶었기 때문이다.
실제 러쉬코리아 싸이트에는 구현되어 있지 않은 Load More 기능을 구현하고 싶었다.
프로젝트 시작 전 읽게된 아티클에서 Infinite scroll, Load More, Pagination마다 장단점이 있지만 e-commerce site의 경우, load more 과 pagination이 더 적합할 것이라고 하였다.
그런데 러쉬코리아의 싸이트는 모든 제품이 한번에 보여지게 되어 있는 것을 확인하고 요즘 트렌드에 맞는 Load More 기능을 추가하고 싶었다.
Infinite scroll을 채택한 페이스북이나, 인스타그램과는 달리 e-commerce site의 경우에는 잠재 구매 고객의 시선이 우리 제품에 더 오래 머무를 수 있는 것이 판매량 증대에 도움이 될 것이라고 생각되었기 때문이다.
그리고 제품 상세 페이지의 상품후기 Pagination을 구현해보고 싶었다.
increaseCount = () => {
const { selectedProductQtyInCart } = this.state;
const updateCount = selectedProductQtyInCart + 1;
this.setState({
selectedProductQtyInCart: selectedProductQtyInCart + 1,
});
this.sendToServer(updateCount);
};
setState()는 비동기적이다. 비동기적으로 처리되는 함수와 동기적으로 처리되는 함수를 같이 작성하면 의도한 것과 다른 순서로 브라우저에 반영된다.
사용자가 수량을 조절하면 setState로 state에 변화가 생기지만, 이렇게 바로 변화된 값을 가지고 또 서버로 업데이트 내용을 보내는 함수를 실행하면 의도한 수량 값이 전달되지 않는다.
그래서 state의 변경이 발생함 과 동시에 따로 변수를 선언하여 수량을 증가시키고 이 값을 서버에 전달한다.
팀 프로젝트 전 인스타그램 클로닝에서 경험했던 것을 바로 적용할 수 있어서 더 기억에 남는 코드다.
calculatePrice = () => {
const { selectedProductQtyInCart } = this.state;
const { product, calculateTotalPriceInCart } = this.props;
const totalPrice = selectedProductQtyInCart * product.price;
calculateTotalPriceInCart(totalPrice);
return exchangeCurrency(totalPrice);
};
state는 중복을 배제하고 최소 집합으로 만들어져야 한다.
총액을 state로 관리할 수 도 있겠지만, React의 원칙대로 DB에서 받아오는 각 제품들의 수량, 금액 정보를 가지고 총액을 계산하는 로직을 Front에서 구현하였다.
export const exchangeCurrency = price => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'KRW',
}).format(price);
};
Backend에서 주는 제품의 가격은 단순한 number type으로, 단위 구분이 없다. 이 숫자를 원화로 표시하는 방법을 알아보다가 new Intl.NumberFormat()을 발견하였다. 여러 나라의 통화를 바로 나타내 줄 수 있을 뿐만 아니라, 그 통화에 맞는 단위로 변경도 가능하다.
처음에는 금액이 필요한 모든 곳에서 조금은 복잡해 보이는 이 방법을 사용하였는데, 멘토님의 조언으로 utiliy function으로 따로 빼서 필요한 곳에서 호출하여 사용할 수 있도록 변경하였다.
결과적으로 코드의 가독성이 좋아졌다.
export default class Pagination extends Component {
render() {
const { reviewsPerPage, totalReviews, paginate } = this.props;
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalReviews / reviewsPerPage); i++) {
pageNumbers.push(i);
}
return (
<nav>
<ul className="pagination">
{pageNumbers.map(number => {
return (
<li key={number} className="pageNumber">
<Link to="#/" className="pageLink" onClick={() => paginate(number)}>
{number}
</Link>
</li>
);
})}
</ul>
</nav>
);
}
}
원래 계획은 Backend에서 상품후기 pagination이 가능하도록 하는 것 이였는데, 계획에 변경이 생겨 Frontend에서 구현하게 되었다.
내가 Mock-data로 만든 50개의 상품후기를 한 페이지당 5개씩 보이도록 하여 총 10쪽의 페이지가 생기도록 하였다.
댓글이 증가할 것을 대비하여 페이지 수는 상품 후기의 수와 한 페이지당 보여지는 상품 후기 수에 따라 자동 계산이 될 것이고, 해당 숫자를 클릭했을 때 state로 관리되는 현재 페이이지는 변경이 되면서 해당 페이지로 이동한다.
처음에는 페이지 숫자를 클릭하면 페이지가 바뀌는 것이 아니라 상세 페이지 상단으로 이동하여 문제가 무엇인지 한참을 찾아보다가 <Link to='#/
>로 변경하면 해결된 다는 글을 참고하여 적용하였더니 원하는 대로 작동하였다!
Backend 담당자분과 추후에 꼭 DB에서 받은 데이터로 pagaination을 구현하자고 약속하였는데 꼭 이루어졌음 좋겠다.
나는 이제 이 컴포넌트가 어디에 있던 state와 props를 자유자재로 옮겨다니며 쓸 수 있다!
한 페이지에 컴포넌트가 점점 많아지면서 선언했던 state의 위치가 부모 컴포넌트로 또는 할머니 할아버지 컴포넌트로 이동해야 하는 경우가 있었는데, 컴포넌트 트리를 작성하여 state 위치를 결정하니 어렵지 않게 모든 컴포넌트에서 상태관리를 할 수 있었다.
처음에는 Life cycle이 생소했다. JavaScript에는 없던 개념이라 이 아이를 언제 왜 써야 하는 지 공부를 했는데, 이론적으로 공부한 것을 실제 프로젝트에 연결하는 것이 처음에는 어려웠다.
분명히 DB로부터 componentDidMount를 통해 데이터를 불러왔는데, 그 데이터에 접근하여 특정 데이터를 뽑아오면 에러가 자꾸 발생하는 것이다.
그렇게 몇 시간을 머리를 쥐어 뜯다가 이 에러의 원인은 Life cycle 때문이라는 것을 알게되었다. 으잉?
Constructor단계에서는 비어 있던 데이터가 ComponeneDidMount를 통해 채워진건데, 비어 있는 상태가 존재하는 데이터에 접근하려 하여 에러가 발생했던 것이다.
이를 해결하기 위해 Front에서 조건부 렌더링을 해줘야 했다. 데이터가 확실히 있을 때만 그 데이터에 접근하도록!! 너 이녀석!!!!!!!!!!!!!!!!!!!!!!!!!
이런 개념이 있는 지 조차 모르고 시작했던 프로젝트였는데 나의 머리 숱과 바꾼 귀한 지식이다.
온 정성을 쏟아 부었던 장바구니 페이지.
Backend 분들께 요청하여 각 제품의 is_checked: true
가 default인 상태로 데이터가 넘어오도록 하였고, 장바구니 상에서 모든 제품이 is_checked: true
면 checkall box도 자동으로 true가 되도록 구상하였다.
문제는, state로 관리 중인 제품의 is_checked 상태가 사용자에 의해 false나 true로 변경되었을 때의 로직이었다.
처음에는 조건문으로 e.target.checked 를 확인하여 state를 변경하였는데, 처음 시도했던 로직은 state를 직접 변경하는 것이 되어 immutability라는 React 대원칙을 어기게 된것이다.
멘토님의 지적으로 해당 로직을 수정하면서 가장 오래 고민했던 만큼 React 대원칙을 다시 한번 마음속 깊이 새기게 되었다.
시간적으로 프로젝트의 절반이 지난 어느 날, Frontend 1명이 그만두었다.
다른 조보다 원래도 1명이 적은 상태로 시작했었는데, 이제 우리는 Frontend 2명이 된 것이다.
절대적으로 부족한 인원과 시간 속에서 우리는 선택을 해야 했다.
두번째 스프린트 미팅에서 이 팀프로젝트가 누군가에게 멋지게 보이기 위해 진행되는 것이 아니라 Frontend, Backend 모두 그 동안 배웠던 것들을 총동원하여 복습하고 주도적으로 새로운 것을 배워가는 것임에 의미를 두기로 팀원들과 상의를 하고서, 버릴 것은 버리고 취할 것은 취하는 것으로 결정하였다.
예쁨과 기능 욕심을 버리고 필수 기능을 선택하자...ㅠㅠ
비록 Frontend 2명이라는 열악한 조건이었지만 동료 Frontend와 더 똘똘 뭉쳐 끝까지 함께 할 수 있었기에 그 고마움을 다시 한 번 전하고 싶다.
인원이 부족해지면서 내가 맡은 페이지가 많아졌다.
특히나 장바구니 기능은 모든 페이지에서 연결이 되어있기 때문에 수정 사안이 생기면 이 컴포넌트 저 컴포넌트 줄줄이 사탕으로 변경이 발생하였다.
그러다보니 code review하는 리뷰어도 혼란스럽고 나도 혼란스러운 상황이 반복되었다.
보통은 이렇게 혼자 많은 페이지를 담당하지는 않기 때문에 어쩔 수 없는 경우라고는 하였지만, 개인적으로 git 사용이 초보여서 더 어려움이 있었던 것 같다.
2차 프로젝트 시작 전에 git을 공부하는 것을 다짐하였다.
같은 맥락에서 싸이트다운 싸이트를 만들고, Backend에서 만들어준 데이터를 잘 보여주기 위해 집중하다보니 refactoring이 부족하다고 느꼈다.
특히 반복적으로 사용된 함수는 uitility fucntion으로 관리하면 좋을 것 같은데, 일부만 정리가 된 것이 아쉽다.
2차 프로젝트 중에는 엄두를 못 낼 것 같고, 모든 프로젝트가 끝나면 나의 코드를 되돌아 보며 보다 효과적으로 효율적인 코드로 다듬어야 겠다.
돌이켜 생각해보면 아마도 서로가 서로의 영역을 모르는 상태에서 처음 진행된 팀프로젝트여서 인 것 같다.
가장 큰 예로 Backend에서 설정한 key값이 변동이 있을 때에는 Front에 알려주어야 하는데 먼저 전달받은 내용이 없던 것이 아쉬웠다. 갑자기 되던 기능이 안되는 상황이 발생하면 내가 뭘 잘못 작성했나 한참 고민하며 적지 않은 시간을 허비하였는데, 알고보면 Backend쪽의 key값이 소문자에서 대문자로 바뀌었다거나 아예 다른 것으로 바뀐 경우가 있었다.
2차 프로젝트에는 협업 Tool인 Trello를 적극 활용하여 서로의 소중한 시간을 조금 더 생산적으로 쓸 수 있도록 해야겠다.
처음에는 상대적으로 쉬워보이는 싸이트였다. 그래서 우리 팀은 Front를 다름 팀보다 적게 할당해 준것인가 했는데, 페이지를 하나하나 만들어감에 따라 숨어 있는 기능들이 계속 나타났다.
Backend쪽에서도 Data modeling에 가장 큰 시간이 할애가 되는데 어떤 기능에 어떤 데이터가 필요한 지에 대한 사전 협의가 부족해서 중간에 추가 요청을 하는 상황이 생겼었다.
이 또한 모두 처음이기 때문에 생길 수 밖에 없는 일이었을 것이다.
2차 프로젝트에는 Backend와 함께 철저하게 싸이트를 분석 후 필요한 데이터, 필요한 기능들을 정리하고 시작하면 좋을 것 같다.
발표때 울컥하신 민정님 모습보고 저도 울컥했어요 ㅜㅜ 민정님,, 정말정말 수고 많으셨어요 👍🏻👍🏻
다음 프로젝트도 같이 화이팅해요!❤️