- 클론코딩 : 개발에만 집중할 수 있도록 기존 웹의 기획과 디자인을 참고하여 하나의 사이트를 완성해보는 프로젝트
- 클론 사이트 : STAY FOLIO
- 프로젝트 기간 : 22.03.28 ~ 22.04.08 (2주)
- 프로젝트 참가 인원 : 5명 (풀스택 5명)
사이트 선정 시 팀원들과 만들어보고 싶은 사이트를 정하기가 어려워 팀원들과 여러 사이트들을 보며 서로 구현하고 싶은 기능들을 말해보자 하였다.
그리고 구현해보고 싶은 여러 기능들을 한 사이트에 구현하기에 가장 적합한 사이트를 찾았다.
Front-end Repository
Back-end Repository
React
,Sass
,Router
,http-proxy-middleware
,moment.js
Node.js
,Express
,MySQL
,Prisma
,Bcrypt
,JWT
,CORS
Git
,Github
,Slack
- Nav
✔️ 페이지 상단 Nav bar 구현
- Modal
✔️ 국가 선택시 하이라이트 되는 국가 선택 모달 구현
✔️ moment.js 라이브러리를 활용하여 체크인, 체크아웃 구간을 선택 시 하이라이트하여 보여주는 캘린더 모달 구현
✔️ 모달 창 클릭 시, 뒷 배경 스크롤 방지 기능 구현
- Slide
✔️ 슬라이드를 넘길때 Fade in & out 이 되는 슬라이드 구현
- Token storage
✔️ 로그인 시 세션스토리지에 토큰 저장하는 기능 구현
✔️ 특정 로그인이 필요한 작업 실행 시 저장된 토큰을 백엔드로 보내주는 기능 구현
- Signup API
- Authorization Middleware
✔️ 로그인이 필요한 작업 실행 시 백엔드로 넘어온 토큰을 검사 후, 다음 로직으로 넘기는 기능 구현
작년 처음 수강했던 프론트엔드 온라인 강의를 통해 개인 프로젝트를 진행해 본적은 있지만, 그땐 자바스크립트나 리액트라는 언어를 이해하고 활용하는것 자체에 어려움이 많아 단순히 기능을 구현함에 있어서 많은 어려움을 겪었다. 회고 또한 개인 프로젝트때에는 해야한다는 생각이나 인지조차 하지 못했다.
처음 팀프로젝트를 하면서 느꼈던 개인프로젝트와의 차이점 중 하나는 나만 현재 진행상황이나 이슈에 대해 이해한다고 해서 되는게 아니라, 모든 팀원들이 서로의 작업상황이나 이슈들에 대해 최신 상태로 업데이트가 되어 있어야 한다는 것이였다.
특히나 이번 프로젝트에서 하나의 메인페이지를 다른 팀원 한분과 함께 나누어 작업을 진행하였다.
그러면서 서로의 진행상황이나 사소한 이슈 하나까지도 혹여나 '눈덩이 효과'처럼 나중에 큰 이슈로 번지지 않게 하려고 자주 소통하고 이슈들을 바로바로 해결하려고 노력하였고, 덕분에 큰 문제 없이 함께 메인페이지를 만들어 낼 수 있었다.
(내 의견 적극적으로 들어주시던 정민님 쵝오...👍)
momnet.js는 기존 JavaScript의 Date객체에 비해 원하는 모양의 날짜로 가공이 쉽고, 또한 원하는 년, 월, 일을 추출하거나 날짜를 비교하는데에 있어 훨씬 수훨하여서 프로젝트 기간내에 원하는 모양의 캘린더를 만들어내기에 적합하다고 판단하여 사용을 하였다.
그러나 회고를 하는 지금 시점에서 아쉬운 점은, 프로젝트 기간내에 원하는 기능을 구현하기 위해서 moment.js 라이브러리에 대해 기능적으로만 숙지를 하고 캘린더 기능을 구현하기에 바빴다 라는 점이다.
왜 만들어졌는지, 장점 혹은 단점이 무엇인지 그리고 현재 momnet.js 의 개발이 중지되었는데 (뒤늦게 회고록 작성기간에 추가적으로 공부하면서 알게 되었다.) 왜 중지가 되었는지 등에 대한 것들을 그때 개발을 하던 시기엔 더 깊이있게 다루고 공부하지 못했다.
팀프로젝트를 진행하면서, 내 코드를 다른 팀원들이 보기도하고, 나도 다른 팀원들의 코드를 보고 이해를 해야하는 일이 많이 있었는데, 다른 팀원들의 코드를 보며 이해하는 일에도 많은 시간이 들었다.
그러면서 문득 내 코드를 다른 팀원들이 보았을때에도, 나와 비슷한 시간 혹은 나보다 더 많은 시간을 내 코드를 보고 이해하는데 할애를 하고 있진 않을까 라고 생각했다.
그럼에도 한정된 시간내에 기능 구현에만 집중을 하다보니, 금새 가독성에 대한 생각은 까맣게 잊어버리고 기능 구현을 위한 코드짜기에만 몰두해 있었다.
앞으로 코드를 구현함에 있어서, 우선적으로 주석이 필요없는 가독성 좋은 코드를 짜기위해 노력 하겠지만, 부가적으로 주석을 활용하여 어떤 기능인지 표시해두거나, 코드만으로 이해가 어려울 경우 부가적인 설명을 달아두거나 하는등의 연습도 필요한 것 같다.
(정성이 담긴 1600줄 짜리 signup 페이지 잊지모태...)
import React from 'react';
import moment from 'moment';
import style from './TwoCalendars.module.css';
// 상태값 관리 와 구현한 기능은 모두 부모 컴포에서 관리해주고
// 여기서는 받아온 값으로 달력을 보여주는 기능만 존재합니다.
function TwoCalendars({
stateMoment,
checkIn,
checkOut,
onCheck,
tempoCheckOut,
onHover,
onHoverReset,
}) {
// 달력 생성을 위한 변수
const thisMonth = stateMoment;
const thisFirstWeek = thisMonth.clone().startOf('month').week();
const thisLastWeek =
thisMonth.clone().endOf('month').week() === 1
? 53
: thisMonth.clone().endOf('month').week();
const nextMonth = stateMoment.clone().add(1, 'month');
const nextFirstWeek = nextMonth.clone().startOf('month').week();
const nextLlastWeek =
nextMonth.clone().endOf('month').week() === 1
? 53
: nextMonth.clone().endOf('month').week();
const checkInDay = checkIn;
const checkOutDay = checkOut;
const tempoCheckOutDay = tempoCheckOut;
// 특정 날짜마자 어떤 조건에 해당하는지 판단하여 조건에 맞는 className 주는 함수
const checkDate = days => {
if (days.format('YYYY-MM-DD') === checkInDay) {
return style.checkInDay;
} else if (days.format('YYYY-MM-DD') === checkOutDay) {
return style.checkOutDay;
} else if (
moment(days.format('YYYY-MM-DD')).isBetween(checkInDay, tempoCheckOutDay)
) {
return style.tempoCheckOutDay;
} else if (
moment(days.format('YYYY-MM-DD')).isBetween(checkInDay, checkOutDay)
) {
return style.onDay;
} else {
return style.days;
}
};
const calendarArr = (today, firstWeek, lastWeek) => {
let result = [];
let week = firstWeek;
for (let i = week; i <= lastWeek; i++) {
result = result.concat(
<tr key={i}>
{Array(7)
.fill(0)
.map((data, index) => {
let days = today
.clone()
.startOf('year')
.week(i)
.startOf('week')
.add(index, 'day');
if (
moment().isAfter(days.format('YYYY-MM-DD')) &&
moment().format('YYYY-MM-DD') !== days.format('YYYY-MM-DD')
) {
return (
<td className={style.lastDays} key={index}>
<span>{days.format('D')}</span>
</td>
);
} else if (days.format('MM') !== today.format('MM')) {
return <td key={index} />;
} else {
// checkDate 함수의 조건에 따라 다른 className을 부여.
return (
<td
className={`${checkDate(days)}`}
onMouseEnter={() => onHover(days.format('YYYY-MM-DD'))}
onMouseLeave={onHoverReset}
onClick={() => onCheck(days.format('YYYY-MM-DD'))}
key={index}
>
<span>{days.format('D')}</span>
</td>
);
}
})}
</tr>
);
}
return result;
};
return (
<div className={style.calendarWrapper}>
{Array(2)
.fill(0)
.map((el, idx) => {
return (
<div className={style.calendar} key={idx}>
<div className={style.month}>
{idx === 0
? stateMoment.format('M월')
: nextMonth.format('M월')}
</div>
<table className={style.calendarTable}>
<thead className={style.week}>
<tr>
{['일', '월', '화', '수', '목', '금', '토'].map(
(week, idx) => {
return (
<td className={style.week} key={idx}>
{week}
</td>
);
}
)}
</tr>
</thead>
<tbody>
{idx === 0
? calendarArr(thisMonth, thisFirstWeek, thisLastWeek)
: calendarArr(nextMonth, nextFirstWeek, nextLlastWeek)}
</tbody>
</table>
</div>
);
})}
</div>
);
}
export default TwoCalendars;
moment.js를 활용하여 직접 커스텀 캘린더를 만들었다.
구현한 기능중에 가장 시간을 많이 들여서 더욱 기억에 남기도 하고, 많은 시간을 들여 성공한 만큼 뿌듯함도 컸지만, 앞선 회고에서 언급한 것처럼 moment.js에 대한 제대로된 공부를 하지 않고 기능 구현에만 신경을 쓴게 아쉽기도한 코드이다.
2개의 달력을 동시에 보여주어야 해서 '현재 달' , '다음 달' 에 해당하는 변수들을 선언 한 뒤, 함수를 생성하는 함수로 함수를 만들어서 보여줬다.
캘린더를 생성하는 calendarArr 함수에 배열을 jsx문법으로 html 태그안에 넣어서 사용하면 아래와 같이 나오게 되는 점을 활용하였다.
cosnt arr = [1, 2, 3, 4, 5]; // 중략 return <div>{arr}</div>; // 12345
특정 날짜마자 오늘자 기준 이전 날짜이거나 혹은 호버나 클릭 이벤트로 날짜에 하이라이트도 되어야 해서 처음엔 각각 조건에 맞게 랜더링이 되도록 if 문을 걸어 주었다.
이후에 무분별한 if문들로 인해 코드가 길어지고 가독성도 떨어진다고 판단하여 className만 다르고 나머지는 중복되어있는 코드를 없애고 className만 조건에 따라 다르게 출력이되는 함수를 이용하여 className만 변경이 되도록 리팩토링을 해주었다.
const [scrollY, setScrollY] = useState(0);
const [modalActive, setModalActive] = useState(0);
const modalWhereRef = useRef();
const modalWhenRef = useRef();
const openModal = e => {
e.currentTarget.id === 'modal1'
? setModalActive(1)
: setModalActive(2);
setScrollY(window.pageYOffset);
};
const closeModal = () => {
setModalActive(0);
setScrollY(0);
};
useEffect(() => {
if (modalActive === 1) {
modalWhereRef.current.style.display = 'block';
} else if (modalActive === 2) {
modalWhenRef.current.style.display = 'block';
} else {
modalWhenRef.current.style.display = 'none';
modalWhereRef.current.style.display = 'none';
}
}, [modalActive]);
useEffect(() => {
if (modalActive) {
document.body.style.cssText = `
top: -${window.pageYOffset}px;
position: fixed;
width: 100%;`;
}
return () => {
const scrollY = document.body.style.top;
document.body.style.cssText = '';
window.scrollTo(0, parseInt(scrollY || '0') * -1);
};
}, [modalActive, scrollY]);
모달 창 팝업 시, 기존에 스크롤이 가능했던 페이지의 스크롤을 방지하는 코드이다.
modalActive
,scrollY
의 상태값이 변화함에 따라, useEffect가 실행이되는데,
modlaActive
의 상태값이 바뀌게 되면 현재 스크롤 값(scrollY
)을body
의top
으로 저장함과 동시에,body
의position
을fixed
로 바꿔주면서, 스크롤이 불가능하도록 설정하였다. 그리고width
값을100%
로 주면서, 페이지가 깨지는것을 방지해 주었다.모달창을 닫게되면, useEffect의
return
을 활용하여 모달창이 언마운트되는 시점에body
에 적용했던 css들을 기존의 상태로 돌려 놓았다.
기존에는 스크롤 Y축의 값을 상태값으로 관리를 하지 않고,
window.pageYOffset
을 아래와 같이 useEffect 내에서 바로 사용하였다.useEffect(() => { if (modalActive) { document.body.style.cssText = ` top: -${window.pageYOffset}px; position: fixed; width: 100%;`; } return () => { const scrollY = document.body.style.top; document.body.style.cssText = ''; window.scrollTo(0, parseInt(scrollY || '0') * -1); }; }, [modalActive]);
스크롤 창을 열자마자, 뒷 페이지는 기존 페이지의 최상단으로 되돌아 갔고,
useEffect 내부에서는 콘솔을 찍어봐도 스크롤 Y축의 값이 항상 0으로 나왔다.문제가 발생한 원인에 대한 개인적인 생각은,
modalActive
상태 값이 변하면서 useEffect가 실행이 될 때, 외부에서 값을 받아오는 것이 아니라서 초기에 화면이 랜더링이 되었을때의 스크롤 Y축의 값인 0만 계속해서 담기게 된것이 아닌가 라고 생각했다.🔑문제해결
스크롤 Y축의 상태값도 외부에서 관리를 해주었다.
모달창 실행 버튼을 누르면 해당 상태값이 현재 스크롤 Y축의 값으로 저장이 되게 하였고, 그 바뀐 상태 값을 useEffect에 전달해 주었다.
모달창 팝업 시, nav바가 사라지는 문제가 발생 하였다.
둘 다,
position
이fixed
였으며,z-index
의 경우, 모달창이 1순위, nav바가 2순위인 상태였다.🔑문제해결
position
이fixed
인 경우에top
,bottom
,right
,left
의 위치를 하나라도 지정해 주지 않은 상태로 사용을 하게되면,z-index
가 적용이 되지 않는다고 한다.그래서 확인해본결과 nav바의 위치가 제대로 지정이 되어있지 않은 상태였고, nav바의
top
의 값을 0으로준 이후에는 정상 작동 하였다.