23.05.29 - 23.06.22 (3주 + 추가 1주)동안 엘리스트랙 마지막 팀프로젝트를 진행하며 프론트 5명과 백엔드 1명과 함께 팀장, 프론트엔드 파트, 배포, 발표 경험을 회고해 보려한다.

이번 부트캠프 기간 동안 한 번쯤은 팀장을 해보고 싶다는 생각이 있었으나, 이 전 프로젝트까지는 사실 자신이 없었다. 전 직장 경력 덕분에 기획부터 런칭까지의 프로세스는 파악하고 있었으나 우리는 PM 부트캠프가 아닌 개발 부트캠프이기 때문에 개발 환경 세팅부터 배포까지의 프로세스를 잘 파악하고 있는 사람이 팀장을 해야한다고 생각하고 있었다. 하지만 두 번의 팀 프로젝트와 한 번의 개인 프로젝트를 무사히 마치고 '나도 어느정도 개발 프로세스를 직접 경험해봤다.'라는 생각이 들어 자신감이 생겼고 이번 프로젝트에서 내가 팀장을 해보고 싶다 했다.
다행이도 팀원 분들께서 모두 동의를 해 주셨고 부족한 부분이 있다면 팀원분들께서 많은 도움과 피드백을 주셨으면 한다는 말도 전했다...
프로젝트를 본격적으로 진행하기도 전에 심적인 부담감, 책임감과 체력소모가 대단했으나 팀원 분이 먼저 어려운 일 있으면 함께 해결해나가자고, 시행착오가 있더라도 그건 당연한 것이고 그걸 헤쳐나가는 것도 배움의 과정이라고 해주셔서 정말 마음이 든든해졌고 그 한 마디 덕분에 프로젝트 기간동안 무탈하게 멘탈을 붙잡을 수 있었다. 또 팀원 분들 능력치도 너무 출중하시고 다 함께 으쌰으쌰 하는 분위기를 만들어주셔서 큰 어려움이 없었다!!
엘리스트랙 커리큘럼 상 어쩔 수 없이 프론트를 지원하시는 분들이 많았고, 우리 팀 7명 중 딱 한 분이 백엔드 포지션을 희망하셨다. 나와 팀원 분 중 한 분이 지난 프로젝트 때 백엔드 파트 경험이 있으셔서 작업 분량이 너무 많으면 (많겠지..) 백엔드 작업 분량도 분장받아 진행하기로 했다.
그치만 백엔드 분이 수 많은 api와 이슈를 혼자서 척척 해결해 나가셨고 각자의 파트에 집중하며 프로젝트를 마무리할 수 있었다. (커리큘럼상 MongoDB를 배웠지만 이번 프로젝트에서 MySQL을 사용하기로해서 도움을 주기 어렵기도 했음 ㅠ_ㅠ)
팀원 분들의 거주지가 다양해서 우리는 반강제적으로 100% 온라인으로 프로젝트를 진행하기로 했다. 물론 오프라인으로 만나 작업을 같이 하게 되면 조금 더 수월하게 의사소통도 할 수 있고 팀원들 끼리 유대감도 강해질 수 있다는 장점이 있어 살짝 아쉽긴 했으나 오히려 오프라인으로 잦은 만남을 가졌을 때 이동 시간, 작업 환경, 체력 소모 등을 고려한다면 온라인으로 진행했을 때 더 큰 작업 효율을 낼 수 있다고 생각했다. 우리는 주 소통창구로 게더타운 서비스를 활용하기로 하였고 게더타운의 채팅 내역 휘발성 때문에 디스코드도 함께 사용했다.

처음엔 우리 팀원들 전반적으로 커리큘럼상 배우지 못했으나 흥미로운 기술을 접목시켜 보고 싶다는 욕심이 있었다. 웹소켓과 WebRTC를 사용한 화상채팅 서비스, Ai관련 Api를 활용하는 서비스, Three.js를 사용한 인터랙티브한 사이트 등등 다양한 아이디어가 있었다. 하지만 이번 프로젝트의 취지는 커리큘럼 상 습득한 내용들을 deep 하게 체화하는 것이였고, 새로운 기술을 습득하기엔 프로젝트 기간이 충분치 않고 백엔드 인원도 한 분 이라는 제약이 있었다. 우리는 CRUD 기본에 충실한 서비스를 구현하기로 했다. 처음에 했던 기획이 엎어져서 아쉬웠으나 지금 생각해보면 기존 기획대로 진행했으면 새로운 기술스택을 사용하는 것에 집중하다가 근본적인 기술의 기반이 허술해지고 버그도 많았을 것 같다.
그렇게 우리 서비스의 주제는 'IT 프로젝트 및 스터디 참여인원을 모집할 수 있는 서비스' 로 정해졌다. CRUD 근본에 충실하면서 화려한 기능을 추가하기 보단 디테일한 사용성에 집중하며 seamless한 경험을 할 수 있는 서비스를 구현하는 것을 목표로 정했다.
기획부터 배포, 발표 준비까지 3주라는 프로젝트 일정이 시행착오가 필연적으로 발생하게되는 예비 개발자에게는 넉넉한 기간이 아니라고 판단했다. 게다가 우리는 온라인으로만 협업을 하기 때문에 의사소통이 더더욱 중요하다. 그래서 매일 오후 12시, 저녁 시간은 유동적으로 두 번 스크럼을 하기로 했고, 그 동안 본인이 한 작업, 앞으로 할 일, 풀리지 않은 문제를 공유 하기로 했다. 하루 두 번의 스크럼이 조금 벅찰 수 있기 때문에 30분을 넘기지 않으려고 했다. 그 외의 이슈사항으로 스크럼 시간이 길어질 것 같으면 해당 이슈의 핵심만 간단히 공유하고 그 이슈와 관련있는 분들끼리 각자 소통하고 해결 내용을 공유하는 방식으로 진행했다.
기능 명세는 스프레드 시트에서 작성했고 페이지와 컴포넌트를 구분하여 기능을 작성하였다. MVP가 아닌 디테일한 기능들은 색상으로 구분해서 1차 구현 후 추가 진행하였다.

(API 명세서를 노션으로 옮기면서 기능 명세서도 노션으로 작성하면 더 가독성 좋게 작성하고 체계적으로 관리할 수 있었을텐데.. 살짝 아쉬웠다.)
포스트맨에서 api 명세서를 자동으로 export해주는 기능이 있어 사용해볼까 고려했으나, 그렇게되면 백엔드에서 api를 구현한 뒤 api를 호출하는 코드를 작성하게된다. 우리는 백엔드 파트가 한 명이고 프론트만 6명인 상황이기 때문에 최대한 흐름이 끊키지 않게 작업을 할 수 있도록 API를 구현하기 전 명세서를 작성하고 프론트는 response 값에 맞춰 목데이터와 api 호출 코드를 작성하고, 백엔드는 그에 맞춰 api를 구현하기로 했다. 처음엔 스프레드시트로 명세서 작성 작업을 했으나 셀의 크기가 작아 response 값을 작성하기에 어려움이 있어 노션 테이블 탬플릿을 활용해서 작성하였다.

테이블 형식으로 한 눈에 api 호출 정보와 개발/테스트 여부를 확인할 수 있다.


테이블을 선택하여 상세 페이지 진입 시 response 필드명과 타입을 확인하고 예시를 확인할 수 있다.
엘리스트랙에서 형상관리를 깃랩으로 진행하고 있었기 때문에 Task, QA, 트러블 슈팅을 깃랩 이슈로 작성했다. 마일스톤으로 Task, QA, 트러블 슈팅을 구분하고 그에 맞춰 이슈를 생성하는 방식으로 진행하였다.


마일스톤을 구분에 맞게 선택하는 것 외에는 별다른 양식 없이 이슈를 작성하다보니 제각기 다르게 태스크 단위를 쪼개게 되었다. 프로젝트와 팀이 크지 않아 이런 방식으로도 각자의 일정을 체크하는 것에 무리가 없었으나, 다음 번엔 이런 양식까지 정해서 작성하면 좋을 것 같다. 그리고 깃랩 이슈는 보드 형식으로 봤을 때 충분한 정보가 표시되지 않아 가독성이 떨어지고 캘린더 형이 없어서 일정을 한 눈에 체크하기가 어려웠다. 협업툴로 지라를 많이 사용하던데.. 한 번 사용해 보고 싶다..
기능 구현이 완료되고 QA하기 전 노션 테이블 탬플릿으로 로컬/실서버/모바일을 구분하여 테스트 케이스를 작성하고 이에 맞춰 QA를 진행하였다.

const [isLoading, setIsLoading] = useState(false)const MAX_NAME_COUNT = 50src/pagessrc/pages/Mainsrc/components/Project/ProjectBody.tsxsrc/pages/Main/index.tsxinterface User {
user_id: number;
user_email: string;
user_name: string;
user_password: string;
user_career_goal: string;
user_stacks: {
stackList: string[];
};
user_introduction: string;
user_img: string;
}
export type TypeTeamProjectUser = Pick<
User,
'user_id' | 'user_email' | 'user_name' | 'user_career_goal' | 'user_img'
>;
interface MemberSelectFormProps {
selectedUserList: TypeTeamProjectUser[];
onMemberSelect: (userData: TypeTeamProjectUser) => void;
onMemberUnselect: (userId: number) => void;
}
master
├── develop
│___│___│___*feature-main
프론트와 백엔드 레포지토리를 나누어 작업하였다.
각 레포에서 feature-(기능/페이지 명) 으로 개인 작업 브랜치 생성하고, 배포 시에는 VM 서버에서 최신 버전을 merge할 때 충돌 방지용으로 배포용 브랜치도 추가 생성해서 배포했다.
프론트는 매일매일 머지를 진행하며 작업한 컴포넌트와 유틸 코드 등을 공유하였다.
유다시티 스타일을 사용하기로 하였고 최대한 제목에 커밋 메세지를 다 작성하는 방향으로 진행했다.
1주차 - 기획, 디자인, 작업 파트 분장, 작업 파트 분장
2주차 - MVP 기능 구현
3주차 - 추가 기능 구현, QA, 발표자료 준비

이 페이지는 카테고리, 검색, 모집상태 필터링, 페이지네이션(무한스크롤)에 따라 리스팅 조건이 달라지고, api를 호출하는 함수의 매개변수와 변경되어야하는 상태의 조건 또한 달라져서 로직 구현이 까다로웠다.

위의 이미지와 같이 필터링 조건을 바꿀 때마다 각 상태값이 제각기로 변경되어야만 했다.
여기에 더불어 검색 기능은 검색 버튼 없이 검색어를 입력할 때마다 자동으로 검색결과를 도출하는 ui를 구현했는데, 잦은 api 호출을 방지하기 위해 useEffect로 디바운싱을 걸어 api를 호출하였다. 또한 카테고리나 모집상태 구분이 변경되더라도 다른 상태 값들은 기존 상태를 유지해야했기 때문에 이 또한 useEffect로 카테고리, 모집상태 구분이 변경될 때 api를 호출하도록 했다.
때문에 최초로 페이지가 렌더링될 때 중복으로 api를 호출하는 것을 방지하기 위해 isFirstFetch 상태값이 추가되었고, 뒤로가기 버튼으로 프로젝트 리스트에 재진입했을 때에는 기존 필터링 상태값을 유지하고, GNB 혹은 리다이렉트를 통해 진입한 경우엔 초기 필터링 상태값으로 변경하기 위해 위의 상태값들을 전역으로 관리하고 isRefetch 상태값을 추가하여 기존 상태값을 유지하도록 하였다.
// 모집글 리스트 페이지의 useEffect hook 사용 부분
useEffect(() => {
const { isFirstFetch, isRefetch } = projectListState;
if (isRefetch) {
setProjectListState((prevState) => ({
...prevState,
isRefetch: false,
}));
}
if (isFirstFetch && !isRefetch) {
window.scrollTo(0, 0);
setProjectListState((prevState) => ({
...prevState,
isFirstFetch: false,
}));
getProjectListData(1);
}
}, []);
useEffect(() => {
const { isFirstFetch, isRefetch } = projectListState;
if (!isFirstFetch && !isRefetch) {
window.scroll(0, 0);
getProjectListData(1);
}
}, [projectListState.selectedCategory, projectListState.recruitingFilter]);
useEffect(() => {
const { isFirstFetch, isRefetch } = projectListState;
if (!isFirstFetch && !isRefetch) {
const delayDebounceFn = setTimeout(() => {
window.scroll(0, 0);
getProjectListData(1);
}, 700); // 디바운스 타임 설정
return () => clearTimeout(delayDebounceFn);
}
}, [projectListState.keywordValue]);
개인적으로 isFirstFetch나 isRefecth와 같은 상태값을 추가하여 위와 같이 useEffect의 렌더링 기능을 제어하는게 깔끔하지 않은 것 같아서 조금 더 공부해보고 리팩토링 해보고 싶다.


이 페이지 내에서 가장 큰 챌린지는 에디터의 이미지를 서버로 전송하는 것이였다.
문제점
해결방법
<img src={base64코드}/> 를 찾아 <img src={서버주소/변환한 이미지파일명}>으로 변환const handleSubmitClick = () => {
// 에디터 HTML string
const editorHTML = quillRef.current.root.innerHTML;
// html코드 내 base64 코드를 찾아 이미지 파일로 변환하는 유틸함수 사용
const editorImgFiles = base64sToFiles(
findBase64(editorHTML),
`${loginData ? loginData.user_id : 'e'}-${new Date().getTime()}`
);
// 에디터 이미지 서버 경로 추출
const urls = editorImgFiles.map((file) => `${IMG_DOMAIN}/static/portfolio/${file.name}`);
// base64 => 에디터 이미지 서버 경로로 대체
const newDescription = base64imgSrcParser(editorHTML, urls);
// 이미지 파일들을 formData에 append
const formData = new FormData();
editorImgFiles.length > 0 &&
editorImgFiles.forEach((file) => formData.append('portfolio_img', file as File));
//...
}
이렇게 코드를 작성하여 에디터에 이미지를 삽입하는 시점이 아닌 폼을 제출했을 때 이미지가 서버로 업로드 되게하였다.


모달을 공통 컴포넌트로 사용할 수 있도록 children props로 모달창 내 컴포넌트 구조를 잡을 수 있도록 하였다. 추가로 닫기버튼 유무도 옵션으로 선택하여 추가할 수 있도록 구현했다.
// ModalFullScreen.tsx
interface ModalFullScreenProps {
setModalOpen: (newValue: boolean) => void;
children: React.ReactNode;
closeButton?: boolean;
}
function ModalFullScreen({ setModalOpen, children, closeButton }: ModalFullScreenProps) {
// 모달 끄기 (X버튼 onClick 이벤트 핸들러)
const closeModal = () => {
setModalOpen(false);
};
// 모달 외부 클릭시 끄기 처리
// Modal 창을 useRef로 취득
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// 이벤트 핸들러 함수
const handler = (event: MouseEvent) => {
// mousedown 이벤트가 발생한 영역이 모달창이 아닐 때, 모달창 제거 처리
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
setModalOpen(false);
}
};
// 이벤트 핸들러 등록
document.addEventListener('mousedown', (event) => handler(event));
return () => {
// 이벤트 핸들러 해제
document.removeEventListener('mousedown', (event) => handler(event));
};
});
// 풀스크린 모달창은 root 하위에 렌더링 하도록 함
return ReactDOM.createPortal(
<div className={`${styles.container} ${darkMode ? `${styles.darkMode}` : ''}`}>
<div className={styles.overlay}></div>
<div ref={modalRef} className={styles.contentsContainer}>
{closeButton && (
<div className={styles.closeContainer}>
<button className={styles.close} onClick={closeModal}>
<AiOutlineCloseCircle size={24} />
</button>
</div>
)}
{children}
</div>
</div>,
document.getElementById('root') as Element
);
}
export default ModalFullScreen;
배포는 엘리스트랙에서 제공한 서버로 배포하였다.
배포는 다음과 같은 순서로 진행하였다.
- ssh로 vm 서버 접속
- node 설치
- pm2 설치
- nginx 설치
- git으로 FE, BE 레포지토리 clone
- .env 파일 추가
- 빌드 + 코드 난독화
- nginx 설정파일 수정(80포트로 프론트 포트 바라보도록 변경)
- pm2로 프론트 정적파일 -spa로 serve, 백엔드 npm run dev로 start
처음에 배포할 때 빌드된 파일을 배포하지 않고 로컬서버에서 3000포트로 실행시키 듯이 run start로 프론트, 백엔드 서버를 실행시켰다.
우리는 개발할 때 strict 모드를 코드를 제외하고 진행해서 렌더링이 두 번 되거나 하는 이슈는 발생하지 않았지만 네트워크 창을 보니 ws 통신이 지속적으로 나타나고 있었다..
웹소켓을 쓰고 있지도 않았는데 이런 문제가 나서 당황했으나 빌드파일로 재배포를 하고난 뒤 이러한 문제는 더 이상 나타나지 않았다.


팀원 분께서 recoil-persist와 공통적으로 쓰이는 컬러들을 변수화하여 다크모드 기반을 만들어주셨다. 덕분에 우리는 각자가 작업한 페이지에서 각자가 쓴 색상코드를 변수로 바꿔서 페이지 전체에 다크모드를 적용할 수 있었다.
다크모드는 css in js로 구현하는 것이 보편적이고 scss로 하기엔 어려움이 있다고 해서 구현하지 못할거라 생각했는데 발표 하루 전날 합심해서 구현한 덕분에 더더 뿌듯했다.
12분이라는 발표 시간동안 우리의 프로젝트를 얼마나 효과적으로 보여줄 수 있을까?
우리 서비스는 목적과 ui가 직관적이였기 때문에 과감하게 페르소나, 스토리보드, 와이어프레임 등 기획단계의 문서들은 빼고 표지에서 우리 서비스의 정체성을 보여주기로했다.
또한 사용자가 서비스를 진입하는 시점부터, 목적을 달성하기까지 기능들을 보여주어야 했는데, 라이브 시연을 하게되면 인풋과 에디터에 텍스트를 작성하는 시간들이 오래걸리다보니 해당 과정을 영상으로 담았고 타이핑을 하는 과정은 과감히 배속하였고 그에 맞춰 스크립트를 짜고 영상 타이밍에 맞춰 구술하였다.
그 외 부가적이고 디테일한 기능의 시연은 gif로 만들어 비슷한 맥락끼리 그룹핑하여 보여주고 기술스택, 트러블슈팅, 문서화 내용을 순으로 발표를 진행했다.

...진실로요?
솔직히 우리 프로젝트 완성도는 높았다고 자부할 수 있으나
우리는 CRUD기본에 충실한 프로젝트인 반면에 다른 팀들의 화려한 기능 발표를 보고 우수팀에 선정될거란 예상을 전혀 하지 못했다.
우수팀에 선정되면 시상식 날에 추가 발표를 해야하고, 그 날 심사에 따라 최종적으로 대상, 최우수상, 우수팀이 발표되는 것이였다.
발표 시점으로부터 종강일까지 4일이 남았고 휴식을 취하고 있던 팀원들과 나는 급하게 다시 게더에 모여서 최종 발표 때 보여줄 우리 서비스에서 보완할 것들이 무엇이있고, 추가하면 좋을 기능이 무엇이있을지 논의하였다.
우리 서비스는 크게 프로젝트/스터디 팀원을 모집하는 서비스, 모집한 팀이 만든 결과물을 자랑하는 서비스로 구분되어있어, 모집 완료 된 프로젝트/스터디에서 진행한 프로젝트를 자랑하는 사용자 플로우를 기대할 수 있다. 이에 따라 모집 완료글에서 해당 팀이 진행한 프로젝트 게시글을 연결하는 기능이 있으면 좋을 것 같다는 이야기가 나왔다.
모집글을 연결하는 플로우는 크게 두가지로 구분할 수 있다.
- 바로 프로젝트 자랑 작성 페이지로 직접 진입한 경우, 관련 모집글 선택하기 버튼과 모집완료 글 모달으로 내가 작성한 글을 선택하여 모집글을 연결할 수 있다.
- 모집글 작성자는 모집완료가 된 모집 글 페이지에서 바로 프로젝트 자랑 작성으로 이동할 수 있다. 이 경우 해당 모집글을 바로 연결시킨다.
나는 프로젝트 자랑 작성/수정 페이지를 담당하였기 때문에 위의 기준에 맞게 코드를 수정하였다.
1번 케이스에서는 프로젝트 작성/수정 페이지에서 모집글 선택을 누르면 본인이 모집 완료한 글 리스트를 볼 수 있고 사용자가 선택한 모집글의 번호와 title을 찾아 렌더링하고 formData에 모집글 번호만 append하면 된다.
2번 케이스는 모집글에서 바로 '프로젝트 자랑하러가기' 버튼을 통해 프로젝트 자랑 작성 페이지로 넘어오기 때문에 아래와 같은 로직을 추가하였다.
1. 쿼리스트링으로 해당 모집글 번호를 받아, 로그인한 유저의 글임을 확인
2-1. 해당 유저의 글이 아닌 경우 알럿창 띄우고 프로젝트 자랑 페이지로 리다이렉션
2-2. 해당 유저의 글인 경우 유저의 모집 완료한 글에서 같은 모집글 번호를 찾아 title을 추출하고 선택한 모집글 상태에 번호와 함께 추가

반응형 또한 원래 계획에는 없었다. 하지만 백엔드 분이 한 분인 상황에서 기간 내 추가할 수 있는 기능이 한정적이였기 때문에 프론트 파트는 시간이 어느 정도 남은 상황이였다.
그래서 어쩌다보니 반응형도 도전하게 되었다.
처음엔 media query로 반응형을 구현해보려 했으나 css 기준의 레이아웃만 변경할 수 있을 뿐 컴포넌트의 구조 자체를 변경하는데 어려움이 있었다.
컴포넌트 구조를 변경하려면 window 사이즈를 파악해서 데스크탑, 모바일 기준으로 컴포넌트 구조를 구분해야한다. window 사이즈를 파악하는 로직을 직접 짜는 대신 react-responsive 패키지를 사용하여 768px 기준으로 간편하게 데스크탑, 모바일을 구분할 수 있도록했다.
하 추가 발표 준비 기간은 너무 떨리고 힘든 시간이였다.
기존 프로젝트 기간에는 2주차에 MVP 기능을 마무리하고 3주차에 정말 짜잘한 버그를 고치고, 다크모드를 구현하고 발표준비를 나름 널널하게 할 수 있었는데, 추가 발표 준비 4일 동안 추가기능 기획하고, 개발하고, 버그잡고, 추가 발표 준비를 다시 하니 심적 부담감과 체력소모가 너무 컸었다.
그래도 기존에 나름대로 꼼꼼하게 작성해논 발표 스크립트와 기존 발표 때 시간제약으로 아쉽게 추가하지 못했던 발표자료들이 있었기 때문에 발표준비는 생각보다 오래걸리지 않았다.
발표를 서서하는지, 발표시간과 질의응답은 어떻게 이루어지는지까지 철저하게 체크를 하고 그에 맞춰서 발표 연습도 다시했다. 발표 당일 리허설 시간보다 일찍가서 미리 팀원들과도 인사하고 (오프라인으로 첫 만남🫣) 발표 환경, 발표자료를 끊임없이 체크하면서 긴장도 풀어갔다. 후후 (청심환도 먹음)
기존 발표는 온라인 발표이기 때문에 화면공유로 발표자료만 띄워서 발표를 했었지만 이번에는 많은 레이서들과 심사위원들 앞에서 발표를 했기 때문에 표정, 제스처까지도 신경써야했다. (발표 당시에는 긴장되어서 어떻게 발표를 했는지 기억이 잘 안난다........ 😭)
어찌저찌 무사히 발표가 끝나고 대망의 최종 시상식..!!!!
우리는 최우수상을 받게되었다!!! 오예😆
프로젝트를 무사히 마무리한 것 만으로도 뿌듯했는데 상까지 받게 되다니!!
정말 그 동안 공부하고 프로젝트하느라 고생했던 것들을 다 보상받는 느낌이였다!!
우리 팀 정말 최고..😍
리액트의 특성상 컴포넌트가 렌더링 될 때마다 컴포넌트에 포함된 함수들도 재렌더링된다. 컴포넌트 내 다른 각자 다른 이벤트를 발생시키는 컴포넌트가 두 개 이상 있는 경우, 메모이제이션 훅을 사용하면 의존 관계에 있는 함수만 재렌더링되어 불필요한 연산을 줄일 수 있다.
리코일은 외부에 상태를 두기 때문에 서비스가 더 커질 수록 사이드 이펙트를 캐치하기 힘들다는 단점이 있었다. 그래서 어떤 상태들이 쓰이는 컴포넌트들이 명확하고 바뀔 가능성이 없다면 Context API를 쓰는 것이 사이드 이펙트를 파악하기 유용하다.
명령형 코드는 결과물을 어떻게(How) 그리는지, 과정에 집중하고 선언형 코드는 무엇을(What) 그리는지, 결과물 자체에 집중한다. 선언형으로 코드를 작성한다는 것은 복잡한 과정은 추상화하여 코드만 보고 어떤 일이 발생할지 예측할 수 있어 가독성이 좋다.
예시를 많이 찾아보고 조금 더 감을 익힌다음에 명령형으로 작성한 코드들을 수정해봐야겠다.
서버의 데이터와 클라이언트 데이터가 공존하게 되면 서로 상호작용하며 섞이게 될 수 있고, 이 과정에서 사용자는 업데이트 되지 않은 데이터를 확인할 수도 있다고 한다.
그리고 비즈니스 로직을 분리하게되면 유지보수 하기도 쉬워질 것이다.
이 외에 서버 사이드 상태관리 라이브러리를 사용하게되면 데이터 캐싱 기능도 쉽게 사용할 수 있어 요청의 수를 줄이고 렌더링 속도도 빨라지는 이점이 있다고 한다.
모집글 리스트에서 사용자가 선택하는 카테고리, 필터링, 검색과 같은 상태와 그에 따라 서버로부터 받아오는 모집글 리스트 상태의 로직을 구분할 수 있도록 해야겠다.
이번 프로젝트는 지금까지 이론으로 배운 내용들을 체화하는 것과 각자가 한 번 씩은 CRUD 기능들을 구현해 보는 것을 목표로 했기 때문에 때론 재사용할 수 있는 컴포넌트들을 각자가 다시 만들기도 했었다. (ex) 검색 컴포넌트, 모집글 제목/요약글/조회수 등..) 때문에 디자인은 동일하나 다른 컴포넌트를 그리고 있는 경우도 있었다. 리액트 라이브러리의 가장 큰 특장점인 '컴포넌트 재사용'의 의미를 약간 퇴색시킨 것 같아 마음 한 구석이 찝찝하다.. 하지만.. 우리가 못해서, 몰라서 안했던건.. 아니잖아... 경험이자나..
Jest로 테스트 코드도 작성을 시도해봤으나 맘 처럼 쉽게 되지 않았고, 이렇게 하다간 간단한 기능정도만 테스트해 볼 수 있는 수준으로 밖에 테스트 코드를 작성할 수 밖에 없다는 생각이 들었다. 이러면 그냥 직접 육안으로 테스트를 하는게 더 효율적이겠는데..? 테스트 코드를 작성하는 것에 대한 의구심이 생겼다. 코치님께 여쭤보니 테스트 코드를 작성하려면 애초에 설계 단계 부터 테스트를 할 것을 고려했여야 했고, 테스트가 잘 될 수 있는 환경으로 코드를 작성해야 했다고 말씀 주셨다. 이런 상황에서는 서비스 로직과 관련이 없는 유틸성 코드만 테스트를 해보며 경험을 해보라고 말씀주셨다.
아직 테스트 코드 툴과 TDD 개발 방식에 대한 개념이 잘 잡히지 않았기 때문에 이 부분은 이론적으로 더 공부를 해보고 시간이 날 때 차차 공부해봐야겠다.
이렇게 돌아보니, 우리 팀 정말 열심히 했구나..
꼼꼼하고 실행력있고 근본에 충실하고 성실한 팀원들 덕분에 이번 프로젝트도 좋은 성과를 얻을 수 있었던 것 같다. (코치님께서도 우리 팀 육각형 인재라고 했다!!)
지난 프로젝트, 스터디에 이어 마지막까지도 이렇게 좋은 팀원들을 만나다니.. 난 정말 인복이 많다..🥹
지금까지 많은 레이서 분들과 협업하고 대화하며 내가 부족한게 무엇인지 돌아볼 수 있었고, 협업하고 싶은 개발자가 되고 싶다는 생각을 많이 했다.
4개월 동안 쉼없이 달려온 엘리스트랙 교육과정은 끝이 났지만 배움에는 끝이 없다! 다시 시작이라고 마음을 다 잡고, 이제는 탈나지 않게 꼭꼭 씹어가며 깊이 있는 공부를하며 차근차근 성장해나가고 싶다.