엘리스 코딩 SW 엔지니어 트랙 3기 2차 프로젝트 (2022.12.12~12.30)
팀구성 : 프론트엔드 4명 / 백엔드 2명
깃헛브주소 : https://github.com/j2h30728/Project2-Do_green
프로젝트 상세내용 : 노션링크(깃허브의 리드미 내용과 동일함)
이 글은 회고 글이므로 프로젝트에 관한 정보는 위의 '프로젝트 상세 내용'을 확인 부탁드립니다.
정말 배운 것이 많은 2차프로젝트였다.
프론트엔드에 대한 기술스택을 쌓는 동시에, 나의 강점도 약점도 깨닫게 되는 큰 계기였다.
우리팀은 기획이 참 많이 바꼈다.
초기 기획
아래의 주제로 다룬 카드형식의 구독서비스 제공
1. 동물을 기준으로 환경 뉴스레터 구독 서비스
2. 환경관련 챌린지 수행 서비스
- 동물들이 카드가 되어 해당 동물과 관련된 환경 뉴스를 제공
- 환경 관련 뉴스, 토픽, 라이프스타일, 비거니즘 등이 카드가 됨
위의 두 가지의 서비스를 진행하고 싶었으나, 아래의 고려해야 할점으로 인하여 기획을 좀 더 다듬을 필요가 있었다.
프로젝트를 코칭해주는 코치분들 또한, 우리 팀에는 기능 다이어트가 필요하다고 조언을 해주셨다.
고려해야할 점
1. 기획의 두 가지 기능 모두, 기본적인 CRUD 임.
2. 뉴스레터의 주제가 여러개의 카드에 해당 될 경우의 복잡해지는 데이터 계층.
챌린지 수행 기능은 웹서비스의 사용자들의 방문과 참여도를 높이기 위해 추가했었다.
하지만, 챌린지를 수행을 확인하는 방법, 정말로 진행 했는 지의 여부 등을 고려해보니 구현의 어려움이 있었기 때문에 뉴스레터 구독 서비스를 중점적으로 두어 진행하기 했다.
복잡해지는 데이터계층을 해결하는 것이 기획 단계에서 큰 문제점이었는데, 사실 프로젝트가 시작하고 4일째되는 날에서까지도 회의를 진행했기 때문이다.
생각보다 팀프로젝트에서 의견이 많이 나오지 않던 상황이라 내가 먼저 의견들을 제안했었고 기억나는것은 아래와 같다.
a. 동물을 메인. 해당 동물이 카드가 되어 뉴스레터를 제공하고 동물과 관련되지않는 뉴스레터는 창작한 마스코트를 통해 뉴스를 제공
b. 동물이 하나의 카테고리를 되어 여러 종류의 뉴스, 토픽, 라이프스타일 등의 뉴스레터를 제공. (즉, 펭귄이 전달하는 기후뉴스, 호랑이가 전달하는 라이프스타일)
프론트엔드에서는 동물을 포기하고 싶지않았고, 백엔드 단에서는 좀 더 간단한 데이터 계층을 원했다.
그 요구사항이 좀 더 많이 반영된 b의견이 채택되었다.
굳이 이렇게 기획이 정해지는 내용을 나열하는 이유는 내 의견으로 인해, 아래의 항목들을 만족했고 다행히 프로젝트가 다시 진행되었기 때문이다.
의견 충돌과 불화없이,
1. 여러명의 의견을 만족할 수 있으면서,
2. 이미 정해진 기획을 변경하여
3. 기능 다이어트를 진행
팀원들이 모두 의견을 잘 내지않았던,1차 프로젝트에서 배웠던 점은 팀프로젝트에서의 적극적인 자세와 참여도
이를 밑거름을 삼아 2차 프로젝트에서 무언가가 정해져야할 때에 많은 의견을 내려고 노력했다.
그중에 기억에 제일 남는것이 이 기획이 확정되었던 날이다.
최종 기획
환경 뉴스 구독 서비스
-> 환경 관련 뉴스, 토픽, 라이프스타일, 멸종위기종 등을 제공
2차 프로젝트에는 1차 프로젝트와 달리 프론트엔드 담당으로 지원했다.
프로젝트는 팀에 좀 더 기여하고싶어 1차프로젝트가 끝나고 진행한 리액트 열심히 수업을 참여했었지만, 직접 프로젝트가 시작되니 겁이 정말 많이 났다.
프론트엔드는 4명이라 메인 역할 분담은 사다리 타기로 정했다.
1. 메인) 로그인 및 유저 관련 기능 구현
2. 공통 모달 컴포넌트 구현
사실 구현한 것을 나열한다면, 타 팀과 비교해서 나는 많은 구현을 담당한 것은 아니다. 그래도 많이 배웠다는 것은 말할 수있다.
일단, 우리팀이 타팀과의 차별성은 남다르고 힙한 기술스택을 사용했다는 점.
팀프로젝트에 적용한 대다수의 기술스택은 팀장님이 제안해서 진행한 것들이 대다수지만, 진행하면서 나름대로 적용하는 이유나 좋았던 내용도 함께 기술한다.
리액트 수업 진도도 따라가기 급급했던 나에게는 정말 청천벽력 같은 사실이었지만, 그래도 맡은 담당은 수행하려고 열심히 임했다.
- 잠재적 에러발생 감소와 생산성 높은 코드 작성위하여 사용
물론 엘리스트랙에서 타입스크립트를 배우긴했었지만, 이것을 리액트와 함께 쓰려고하니 현기증이 먼저 났다.🤢
프로젝트 초반에는 코드를 치는것 보다, 구글에서 타입을 검색하는 시간을 더 많이 썼던 것 같다.
그래도 지금은 오류를 잡아주는 타입스크립트를 쓰는게 좀 더 마음이 편안해졌다. 물론, 내가 해결하지 못하는 타입오류가 나올때마다 도망가고 싶은 마음은 사라지지는 않지만..
팀장님이 팀원분들께 애니스크립트는 타입스크립트가 아니라고 수정해달라고 했던 스크럼회의가 생각나서 종종 혼자서 웃는다.
- 비제어 컴포넌트로 타이핑 등에 일어나는 리렌더링을 줄이기 위하여 사용
- 입력값에 따른 밸리데이션을 편리하게 적용 가능하여 사용
리액트를 사용해 로그인, 회원가입, 회원정보수정을 구현하는 입장으로써는 최고의 라이브러리 아니었을까?
하지만, 처음에는 사용하기 정말 힘들었다. 검색해봤을때 다들 정말 쉬운 라이브러리다 라며 소개글이 시작하는데, 나는 정말로 공감할 수가 없었었다.
전체적으로 총 3번의 수정을 거쳤다.
아래의 수정을 거쳤어도 아직 만족스럽지 않다.
1. react hook form 을 사용해 form 구현
코드리뷰를 통하여, 공통 컴포넌트로 사용할 수있는 것은 컴포넌트화를 시키는 것으로 조언받음
2.Form, Input으로 공통 컴포넌트화 구현
당시, 리액트와 해당 라이브러리의 이해 부족으로 인해서 복잡해지는 코드 구조와 react-hook-form 에서 제공하는 함수를 원하는 대로 사용하기 힘들어, 코치님께 추가 조언을 요청했다.
3. 2번에서 생성했던 컴포넌트를 변경해서 로그인, 회원가입, 회원정보수정 컴포넌트에서 react-hook-form 으로 form을 간단하게 구현.
나머지 부수적인 것들을 공통 컴포넌트로 묶음
CSS라이브러리로 tailwind을 쓰고있었기 때문에 해당 공통 컴포넌트에 함께 작성하였다.
관련된 컴포넌트들을 아래와 같이 정리하였다.
├── components
│ ├── auth
│ │ ├── Login.tsx
│ │ ├── Register.tsx
│ │ └── yup.ts
│ ├── common
│ │ ├── FormsAboutInput.tsx (input,errorMsg,button)
│ │ ├── ImgContainer.tsx (input,img,label)
│ │ └── InputContainer.tsx (input,label)
해당 코드들을 작성하면서 빠르게 완성해나갔던 타팀원을 보며, 많이 주눅들기도했었다. 그래도 남과 비교하는 것이 아닌 과거의 나와 비교하며 열심히 만들었었다.
- 팀에서 사용한 이유는 여러가지가 있겠지만, 나는 '원하는 작은 스토어를 여러개 생성 가능' 이부분이 좋았다.
최종 구현에서는 zustand 를 사용하지 않았지만, 시도하려다 잘못된 사용을 깨달았던 상황이 있었다.
확인 메시지와 에러메시지를 띄워 주는 모달창을 구현할때, react-qeury의 option인 onSuccess와 onError메서드 내에서 제공하는 데이터를 사용하려고 했었다.
이를 활성화된 컴포넌트창에 메시지를 전달하기위해서 zustand를 사용했었다.
잘못된 전역 상태관리로 인해서 회원정보를 수정했는데 회원가입을 성공했다고 메시지가 뜨더라.😱
프로젝트 초반에 코치님이 zustand는 전역과 로컬상태관리를 구분하는것이 힘들 것이다라고 첨언을 해주셨었는데, 딱 그 말씀이 떠올랐다.
이 메시지는 로컬으로 관리하는 데이터라는 것을 깨달음과 동시에 react-query에서 반환 값으로 오는 error와 isSuccess를 통해 메시지 모달을 구현했다.
(초반에는 반환값으로 error 확인하지못하고 위의 삽질이 일어난 것이다.
😅 그래도 팀원과 함께, 일련의 사례로 배운것들이 있어 좋았다.
- 서버데이터와 클라이언트 데이터를 분리하기 위함
- react query 에서 제공하는 다양한 옵션들이 너무 좋았다!
와 정말 이 react query를 사용하자고 먼저 의견내주신 팀장님께 항상 감사한 마음을 전한다. (사실, 음성으로도 채팅으로도 수십번은 전달했던 것 같다.)
이 팀프로젝트가 아니었으면 react query에 대해서 먼저 찾아보고 사용해보려고 도전을 못해봤을 것 같다. 팀장님이 말씀하시기 전에도 이름만 스쳐지나간 친구이지, 직접 써본적도 없다.
get요청을 보내는 useQuery 는 팀장님이 먼저 틀을 만들어 작성해주셨는데, useMutation의 틀은 내가 작성해야할 상황이었다.
그저 post 요청만 보내면 되는데, 파일을 분리해 useMutation을 사용해 해당 컴포넌트에 불러오는 것이 왜 안되는건지!
꼬박 반나절 고민하다 팀장님께 도움을 요청하고 둘이서 한두시간 더 고민하다 해결했다..!
당시 내용을 팀 디스코드에도 기록했었다.ㅋㅋㅋ
사진의 맨아래의 에러 사진은 잘못된 예시의 코드를 작성했을 경우에 뜨는 에러이다. 잘못된 방식으로 hook을 불렀으며, mutate시에 null값이 반환된다는 내용이다.
이 일련의 사례를 통해, useMutation을 이용해, 로그인, 회원가입, 회원정보수정, 이미지 s3 url 요청 등을 구현했다.
팀내에서 실력이 제일 없다고 느껴지면서, 짧은시간내에 많은 것을 할 용기는 없었기에, 이 마인드는 남을 힘들게 하기보다는 나 자신을 더 힘들게 했던것 같다.
그래서 이런 마인드를 역으로 사용해 큰 기능은 수행하기보다,
팀원들이 여러번 시도 하다 고치지 못했던 에러 수정작업을 했다.
아래는 기억에 남던 사항
drawer가 나올 경우, header와 footer 사이에 공백이 존재하며, header가 z-index를 가지는 컴포넌트보다 아래에 구현됨.
프론트엔드, 백엔드 통합 오피스아워 시간의 프로젝트 회고시간 때, '내가 이 팀에서 제일 부족하다는 것을 느꼈고 팀에 민폐를 끼치지않고 맡은 일을 다하기 위해서 정말 노력했다.'라고 말을 꺼냈었었다.
그에 대해서 코치님이 그런 생각이 들때가 제일 좋은 상황이고 더 많은 것을 배울 수 있는 기회라고 말씀하셨었다.
이 말에 지금도 공감하고있다. 팀 프로젝트를 통해, 팀원들을 코드를 확인하고 뜯어보고 같이 에러를 잡아 나가는 여러 행동들이 내 지식의 많은 밑거름이 되었다.
확인메시지창에서는 확인을 누르면 데이터를 서버로 보내고 취소를 누르면 확인 메시지창만 끄게하는 것! 미리 만들어둔 모달 컴포넌트와 함께 react hook form과 함께 쓰려고하니 어떻게 써야할지를 모르겠더라..
이렇게 로직은 써두고 직접 구현하는것은 참 어려웠다.
1. form 을 버튼을 눌러 submit 함
2. 확인 메시지창이 뜨고 확인 메시지창을 누르면 모달 컴포넌트에 props로 setConfirm을 보내어 useState로 true 값으로 변경
3. confrim = true 이면, 렌더된 컴포넌트에서 보내지는 데이터를 서버로 보냄
4. 만약 메시지창에서 취소를 누르면 확인 메시지창만 꺼지고 렌더된 컴포넌트에서 데이터 이동은 없음
로직을 나열해보니 참 간단해보이지만, 당시에 정말 막막했었다.
확인 메시지와 에러메시지를 알려주는 모달창은 같은 hook을 사용하고 있기 때문에 모달이 원하는대로 켜지고 꺼지지 않는 경우도 생겼다.
회원 정보 수정은 제출을 누르면 한 번더 확인하는 로직으로 구현했다.
- 회원 정보를 수정 하고 제출 버튼을 누름
- 확인 메시지창이 뜨고 확인을 누르는 동시에
- 입력값의 오류로 인하여 에러메시지 창이 뜸
- 에러 메시지창에서 확인을 눌러 에러 메시지창을 닫음
문제는 4번에서 일어났다.
에러 메시지창에서 확인을 누르면 에러 메시지창이 닫히는 것이 아닌 확인 메시지창이 켜지고 꺼지더라!!!!😭
수정 전
수정 후
아래의 코드로 변경했다.
isOpen === true
: form 제출 버튼을 누를경우 확인 메시지창 오픈!isOpen
: 확인 메시지창이 꺼진 상태(제출 전 또는 메시지창에서 확인버튼을 누름)isEditError === true
: 회원 정보 수정 mutate 실패confirm
: 메시지창에서 확인 클릭 여부 (누를경우 true)..
useEffect(() => {
//확인 메시지창이 꺼진상태에서 회원정보수정 mutate에서 에러가 있을 경우에 에러 메시지창이 오픈되게함
isEditError && !isOpen ? setErrorModal(true) : setErrorModal(false);
}, [confirm, isEditError]);
// 에러 메시창을 닫음
const handleErrorModalClose = () => {
setErrorModal(false);
};
..
<>
{isOpen ? (
<DialogModal title="내 정보 수정" message="수정 하시겠습니까?" type="confirm" setConfirm={setConfirm} onClose={handleClose} />
) : null}
{errorMdoal ? (
<DialogModal title="오류" message={editErrorMsg} type="alert" onClose={handleErrorModalClose} />
) : null}
{isEditSuccess && !isOpen ? (
<DialogModal title="내 정보 수정" message="수정되었습니다." type="alert" navigate="/mypage" onClose={handleClose}/>
) : null}
{isImgUrlError && !isOpen ? (
<DialogModal title="오류" message={imgErrorMsg} type="alert" onClose={handleClose} />
) : null}
</>
(지금 이 글을 쓰면서 코드를 확인해봤는데, 왜 회원탈퇴은 컴포넌트로 따로 빼지않았나 싶다......왜 그랬지?!)
이전의 프로젝트에서 배운점은 협업에서의 '적극적인 자세'와 '참여도'였다면,
이번 프로젝트는 '소통'과 '책임감'이었다.
프론트엔드 담당자로써 백엔드와 소통할 때 불화가 없도록 나름 노력을 했었다.
예를들면, 위에 기능다이터와 같이 의견차이가 있다면 프론트엔드와 백엔드의 입장을 먼저 나열하는 것이다.
또한, 프론트단에서 api를 다룰때에 에러가 있을경우에 세가지를 꼭 첨부하여 백엔드 담당자에게 전달했다.
- 내가 하려고하는 api 호출과 기댓값
- 기댓값과 다른 결과 값
- 에러 메시지창
사실 따로 블로그에 대화이미지를 첨부하려고 생각하고 행동한 것은 아니라, 깔끔한 대화와 이미지는 없어서 여러개중에 검색에 제일 먼저 걸린 대화를 캡쳐 해왔다.
원하는 바를 확실히 전달하고, 백엔드담당자가 자신이 하고있는 일 스케쥴에 영향 받지 않고 일어난 에러를 바로 해결할 수 있도록 하는 것이 목적이었다.
사실 우리 팀프로젝트는 마지막이 좋은 것은 아니다.
몇 팀원이 불화가 생겨 프로젝트 발표가 끝난 이후, 서버 담당자가 일언반구없이 서버와 데이터베이스를 내리고 소통하고 있던 디스코드도 나갔었다.
팀프로젝트 발표가 끝나고 나서, 총 6명이 3주간 진행했던 프로젝트를 아무말 없이 서버와 데이터베이스를 내린 다는 점에서 이 프로젝트를 쉽게 다룬다는 것을 알게 되었고 책임감의 부재를 느꼈다.
물론, 시간이 지나 그 분이 돌아셨고, 대화를 통해 상황을 공유하며 서버와 데이터베이스는 복구해주셨다.
팀장님을 필두로 폴더와 파일, hook들을 리팩토링 하였을때 참 많은 것을 배웠다.
이것이 정말 hook으로 관리 되어야하는것인지?
어느 폴더, 파일에 존재해야하는 함수, 컴포넌트인건지?
이런 것에 대해서 많이 생각하게 되었다.
이 부분에서는 아직도 헷갈리지만, 팀프로젝트를 진행한 전과 후의 나를 비교하자면 많은 것을 얻게되었다.
리액트를 학습하자마자 진행한 프로젝트였던 만큼, 리액트에 대한 지식이 너무 얕았고 자신감이 없었다.
- 어수선한 컴포넌트와 상태관리
- 명확하지 않은 변수명
- 관심사 분리
현재 회원정보 수정 컴포넌트는 굉장히 긴 코드를 가지고있다. 이를 관심사 분리와 컴포넌트화를 더 잘해 보기 좋고 이해하기 좋은 코드를 만들지 못했다는 것에 아쉬움을 느낀다.
현재, 2차 프로젝트를 진행하면서 사용했던 기술스택을 내 것으로 만들기 위해서
두가지를 진행하고있다.
- nomardcoder react-master class : 내 깃허브 링크
- 프리온보딩 프론트엔드 챌린지 1월 : 내 깃허브 링크
이를 통해 엘리스트랙과 2차프로젝트에서 쌓은 지식을 내것으로 만들고, 개인프로젝트를 진행해서 정리할 예정이다.