로그인 한 번으로 가격 비교까지 간편한 해외 명품 직구 사이트 'CATCH FASHION' 클론 프로젝트
Front-end : 정유정 황윤성
Back-end : 범승원 손창효 유병건
프로젝트의 소통은 반숙과 완숙의 균형
노른자가 완숙에 가까울 때 '나는 반숙이 취향인데요?' 하면 안타까운 계란만 하나 더 까인다. 뭔가 기능을 구현할 욕심이 있으면 미리 이야기하고 할 수 있는 모든 가능성에 대해서 열려있어야 한다.
어차피 노른자는 터진다. 이번 프로젝트에서 내가 얻은 가장 큰 수확은 어쨌든 터질 노른자를 팀의 의도와 방향으로 터치는 방법을 깨달은 것이다.
예컨데 CATCH FASHION의 가장 큰 정체성은 CATCH 구매다.
이 CATCH 구매란 해외 직구 상품의 관부가세를 포함한 가격을 한 사이트에서 비교해 가장 저렴한 상품을 구매하는 것을 말한다.
첫 모델링에서 우리는 이 기능을 시간과 인원을 문제로 최저가구매 정도로 구현하고자 했다.
1주차가 끝날 때 쯤 제품 상세 페이지를 구현할 때 우리는 이게 그렇게 단순하게 넘어갈 기능이 아니라고 판단했지만 돌이키기엔 너무 먼 강을 건넜다. 회의 끝에 기존 캐치구매==최저가 보다 납득 가능한 품질 보증 상품으로 변경했다.
그렇다면 우리는 이 프로젝트의 모델링부터 캐치구매를 본래 사이트처럼 구현해야 했을까?
아무래도 정답이 없다. 팀은 한정된 시간과 인원 내에서 납득 가능한 경로를 합의했다. 이 과정 자체가 서로 불쾌하거나 답답한 일이 아니었다는 것에서 성공한 프로젝트라고 느끼며 잘 이끌어준 팀원들에게 감사함을 느낀다. 노른자는 터졌지만 우리의 첫 프로젝트는 여전히 써니사이드 업이다!
다소 적은 인원으로 진행돼 사이트의 많은 기능들을 포기한 것은 아쉬웠으나 첫 프로젝트를 통해 얻은 것은 완성품 이상의 굳건한 기반이다. 그 중 하나를 꼽으라면 망설이지 않고 소통을 짚을 수 있다. 개발자로서 함께 한다는 것은 나와 타인의 역량만큼이나 그 사이의 연결고리가 중요했다.
짧은 2주 동안 백엔드와 프론트엔드 사이에서는 수 많은 균형추가 오고갔다. '시간 내 구현이 가능한가, 무엇이 중요한가, 데이터의 형식은?'. 프로젝트 초기에 모든 것을 딱딱 맞춰서 진행할 수 있다면 베스트겠지만 기능을 구현하다 보면 모델링을 수정해야 하거나 수정된 내용을 다시 돌려야 할 일들이 잦았다. 그 부분에 있어서 단순히 이게 안되니 바꿔주세요 그건 어려운데요 식의 소통은 안하느니만 못했다. 최소한 변경하고자 하는 방식의 당위성을 납득시킬 수 있어야 한다. 그게 가능해야 진짜 개발자의 소통임을 깨달았고 또 그래야만 한다고 믿는다. 그런 점에서 팀의 소통은 중반 들어 점점 건설적으로 성장했고 덕분에 적은 인원으로도 필수 기능들을 완성할 수 있었다.
가장 아쉬웠던 부분은 스스로 짠 코드에 자신이 없어 서버 연동 이슈와 관련해 혼자서 끙끙 앓다 날렸던 이틀이다. 용기를 내 백엔드 팀원에게 '서버 한 번만 확인해 줄 수 있을까요?' 한마디로 문제가 해결된 것이 팀원들의 시간을 갉아먹고 있었다고 아프게 때리는 듯했다.
프로젝트 내의 blocker가 있다면 각자 다른 영역을 담당하더라도 함께 방향을 찾는 것이 결국 조직 전체의 시간을 절약한다. 꾹꾹 눌러서 메모.
프론트 엔드 팀들의 가장 큰 이슈는 단연 슬라이드였다. 자바스크립트로 구현하는 것은 굉장히 쉬웠던 것에 반해 리액트를 처음 접한 나를 포함한 동기들은 이 부분에서 애를 먹었다. 특히나 hook이나 함수형 컴포넌트를 사용하지 않는 방식은 없었다!
물론 슬릭과 같은 API를 사용하면 효율적이고 굉장히 멋진 슬라이드를 구현할 수 있는 것을 안다!
그래도 해보고 싶었다. 리액트만으로 만드는 슬라이드.
rel : 슬라이드 구현 참고 블로그
프로젝트 내에서 필요한 슬라이드는 무한 슬라이드면서 자동으로 넘어가야 했다.
우선 넘기는 것에 집중했다.
<div className="carouselContainer">
<div className="slideWrap">
<div className="slideBox">
<div
className="slideList"
//이동 관련 css
style={{
width: IMGSRC.length * 1140 + 'px',
transition: speed + `ms`,
transform: `translate3d(
${slideIndex * -1140}px, 0px, 0px`,
}}
>
{IMGSRC.map((source, index) => {
return (
<div className="slide" key={index}>
<img
src={source}
alt="슬라이드"
className="slideItem"
id={`${index}`}
/>
</div>
);
})}
</div>
</div>
</div>
<div className="bannerButton">
<button className="prev" onClick={this.prevSlide}>
<i className="fas fa-arrow-left" />
</button>
<button className="next" onClick={this.nextSlide}>
<i className="fas fa-arrow-right" />
</button>
</div>
</div>
slide의 구성 이미지는 가로 1140px을 기준으로 잡았고 이미지 소스는 배열로 가장 하단에 선언했다. 구현하고자 하는 슬라이드 이미지는 3개다.
왼쪽 혹은 오른쪽 버튼을 누르면 해당 이미지의 index값 만큼 transform에 변동값을 매겨 넘어가게 했다.
prevSlide = () => {
this.state.slideIndex > 0
? this.setState({
slideIndex: this.state.slideIndex - 1,
})
: this.setState({ slideIndex: 3 });
};
//이전 버튼 예시 | 현재 슬라이드인덱스가 0보다 클 때만 이전 인덱스의 슬라이드로 이동, 아닐 경우 인덱스 3번으로 이동
그러나 이렇게 구현했을 경우 슬라이드의 이미지가 굉장히 부자연스럽게 이동하게 된다. 왼쪽으로 이동하는 prev버튼이 인덱스 0번에서 3번으로 이동하게 되므로 갑자기 오른쪽으로 넘어가게 된다.
가짜 이미지를 집어넣으면 이 문제를 해결할 수 있다.
1-2-3 이미지 배열에서 3-1-2-3-1 순으로 각각 가짜 이미지를 넣어주고 prev버튼 이벤트에 의해 1번에서 3번(인덱스 1에서 0)으로 이동하는 순간 인덱스 3으로 0초만에 이동시키면 사용자를 속이면서 무한 슬라이드를 구현할 수 있다!
이를 위해 setTimeOut을 사용한다.
//더이상 버튼에 조건이 필요하지 않으므로 수정, speed 변수를 300으로 정하는 이유는 후술
prevSlide = () => {
this.setState({
slideIndex: this.state.slideIndex - 1,
speed: 300,
})
};
//슬라이드가 가짜요소로 이동할 경우 즉 setTimeout이 시작돼 300ms 속도로 이동하는 슬라이 드 이벤트가 끝나면 바로 해당 인덱스로 이동! speed가 0이 되므로 슬라이드이미지가 이동하지 않는 것처럼 속일 수 있다!
prevSlideTrick = () => {
this.state.slideIndex === 0 &&
setTimeout(() => {
return this.setState({ speed: 0, slideIndex: 3 });
}, 300);
};
//componentDidUpdate로 위 함수를 렌더가 진행될 때마다 실행한다!
componentDidUpdate() {
this.prevSlideTrick();
}
우리가 구현하고자 하는 슬라이드는 자동으로 움직이기도 한다!
componentDidMount() {
setInterval(e => {
this.state.slideIndex < 4 &&
setTimeout(() => {
this.setState({ slideIndex: this.state.slideIndex + 1, speed: 300 });
}, 2000);
}, 2000);
}
setInterval은 따로 clearInterval로 해제해주지 않으면 계속 실행된다.(주의)
우리가 구현하고자 하는 슬라이드는 오른쪽으로만 자동 이동하므로 nextslide의 로직을 그대로 사용하면 된다. slide의 인덱스값이 4보다 작을 때는 오른쪽으로 이동하다가 componentDidUpdate에 적용한 트릭 함수가 슬라이드 인덱스 값을 자연스럽게 0으로 옮겨주게 된다. 자동 슬라이드가 어떤 이벤트에 의해 움직이는 것이 아니기 때문에 componentDidMount를 사용해 렌더가 끝나면 자동으로 실행되게 하면 구현하고자 했던 자동 무한 슬라이드가 완성된다!
다만 이렇게 구현할 경우 백엔드 팀원에게 가짜 이미지에 대한 요청을 하는 것이 굉장히 부적절하다고 느꼈다. 수신한 이미지 배열의 첫번째와 마지막 데이터를 잘라서 복사하듯 붙이는 로직을 만들 수는 있겠으나 이 과정이 크게 의미있게 와닿지도 않았다. 팀과의 대화를 통해 배너가 변경될 일이 그렇게 많지 않으니 하드코딩으로 구현하는 것이 맞다고 합의했으나 사실 어떤 방법도 납득이 되지 않았다. hook이나 함수형 컴포넌트를 공부한 후 이 문제를 다시 생각해야 한다.
슬라이드를 만들기 위해서 어떤 키워드를 찾아야 하는지 해당 키워드를 찾아도 어떻게 사용해야 하는지 해메는 과정이 내가 왜 위코드까지 왔는지 다시금 깨닫게 해주었다. 답을 얻고자 온 게 아니다. 같이 답을 찾는 사람들 틈에 있는 것이 얼마나 소중한 기회인지. 여기서는 정답을 말해주는 게 암묵적으로 금기시 되어 있다. 이런 분위기가 나의 시간을 값지게 만들어준다.
비록 다음 프로젝트에서 slick을 사용하게 되면 지금 만든 코드를 사용하지는 않겠지만 지금은 이 기능을 구현한 것만으로도 충분히 행복하다!
" 그 부분에 있어서 단순히 이게 안되니 바꿔주세요 그건 어려운데요 식의 소통은 안하느니만 못했다. 최소한 팀원에게 변경하고자 하는 방식의 당위성을 납득시킬 수 있어야 한다." "프로젝트 내의 blocker가 있다면 각자 다른 영역을 담당하더라도 함께 방향을 찾는 것이 결국 조직 전체의 시간을 절약한다."저도 이번 프로젝트에서 가장 느꼈던 문구입니다. 소통은 항상 더 중요한거 같다고 이번에 생각이 드네요 좋은글 잘 봤습니다! 윤성님 1차 고생많이하셨고 2차때도 화이팅해요~~