시연영상
- 검색, 필터링 기능
- 글쓰기, 댓글 쓰기
- 팝업스토어 커뮤니티
전국에 열리는 팝업스토어들의 정보를 모아 확인할 수 있으며,
커뮤니티의 기능으로 팝업스토어 관련 소통 활성화를 목적으로 가진 웹 프로젝트
- 프로젝트 기능
- 지도를 통해 가까운 팝업스토어를 찾을 수 있고,
- 필터링을 통해 원하는 팝업스토어만 골라 볼 수 있으며,
- 커뮤니티페이지에서 같은 관심을 가진 사람들끼리 소통할 수 있는 웹 서비스 제작 프로젝트 입니다.
- 본인 담당 부분
- 커뮤니티 페이지 (게시판 목록)
- 게시판 카테고리 별로 확인 (전체, 자유, 후기, 모집)
- 팝업스토어 카테고리, 지역, 기간 등 필터링 기능
- 게시글 검색 기능
- 페이지네이션
- 상세 게시글 페이지
- 댓글, 대댓글 생성, 삭제
- 게시글 좋아요, 싫어요 추가 또는 삭제
- 글쓰기 페이지
- 글 생성, 수정, 삭제
- 후기, 모집 게시판 작성 시 해당 팝업스토어 선택
- 후기 게시글 선택 시 평점 선택
- 스토어 필터, 검색 기능
1. CRA가 아닌 Vite 사용
- CRA는 JavaScript로 구성된 Webpack을 사용하는데 속도가 느린편이다.
- 이를 해결하기 위해 Esbuild를 기반으로 만들어진 빌드툴인 Vite를 사용
- Esbuild는 기존 번들러와 달리 자바스크립트(javascript)가 아니라 go언어로 작성이 되었다.
- 그리고 Vite는 ESModule을 사용하여 빠른 개발 시간을 제공하며,
- HMR(Hot Module Replacement)을 지원하여 코드 변경을 실시간으로 반영하여 빠른 개발 및 디버깅을 가능하게 한다.
- HMR은 다른 모듈에 영향을 주지 않고 개발 중인 모듈만을 실시간으로 업데이트 해준다.
2. 상태관리 라이브러리 선택
팀원들 모두 상태관리 라이브러리를 사용해본 경험이 없어, 상태관리 라이브러리는 Redux-toolkit을 사용하기로 하였다.
Redux-toolkit은 Redux의 난이도를 상당히 낮춰주었다.
스토어 생성, 리듀서 작성, 불변성 업데이트 등 많은 것을 해주어 복잡성을 줄여준다.
너무 많은 것을 해주기에 추후 Redux-toolkit을 사용하지 않고, Redux로만 기능 구현해봐야겠다고 생각했다.
3. Redux-toolkit과 React-Qurey를 같이 사용한 이유
프로젝트의 첫 번째 목적은 성장이지만,
위와 같이 Redux-toolkit이 Redux의 난이도를 많이 낮춰주었다.
그렇기에 또 실무에 많이 사용하는 React Qurey를 배우면 좋을 것 같아
프로젝트에 적용하기로 하였다.
하지만 급하게 할려고 하다보니 제대로 사용하지 못해 아쉬움이 남아 프로젝트가 끝난 뒤 개인적으로 API 통신 부분을 React Qurey로 리펙토링을 진행하며 학습하였다.
4. 프로젝트의 폴더 구조
그리고 Redux-toolkit을 사용하면 Duks패턴을 많이 사용한다고 한다.
프레젠테이셔널 컴포넌트(UI담당)와 컨테이너 컴포넌트(상태관리 담당)로 관심사의 분리가 이루어져 UI를 작성할 때 좀 더 집중할 수 있다는 장점 있다.
프로젝트에 적용하면 좋을 것 같아 팀원들에게 의견을 내었다.
그리고 코치님에게 여쭤보니 현재 현업에서는 많이 사용하지 않는 패턴이지만, 학습하기엔 괜찮다고 하셔서 사용을 결정했다.
5. 서버와의 통신 횟수 줄이기
- 게시글의 좋아요, 싫어요 버튼을 누르기 위해 3번의 서버와의 통신이 필요했다.
1. 로그인중인 user의 정보 요청 2. 게시글의 정보 요청 (좋아요 or 싫어요 누른 user 정보의 배열) 3. 2번에서 1번 탐색 후 없다면 증가 요청, 있다면 삭제 요청
개선 방법으로 요청으로 줄일 수 있다고 생각했다.
처음 uesrId를 얻기 위해 user 정보를 요청하고,
userId를 body에 담아 보내면 백엔드에서 직접 배열을 탐색하고 없다면 증가, 있다면 삭제하도록 하면 서버와의 통신이 2번으로
줄일 수 있을 거라 생각해 백엔드 팀원에게 해당 내용을 전달하였고, 적용되어 서버와의 통신을 줄였다.
- 같은 맥락으로 게시글 수정, 삭제 부분에서도 userId 비교 부분을 백엔드에서 담당하여 서버 통신 횟수를 줄였다.
- 그리고 나중에 로그인한 user정보를 전역상태로 관리하게 되어 1번의 통신으로 줄여졌다.
6. 불필요한 데이터 요청
처음 게시글을 설계할 때, 게시글데이터에 comments데이터까지 populate하여 한 번의 요청으로 해결하려 했다.
Feed(게시글) 스키마 - title: string - author: Types.ObjectId <User> - board: string - content: string - images: string[] - store_id?: Types.ObjectId <Store> - ratings?: number - likes: Types.ObjectId[] <User[]> - reports: Types.ObjectId[] <User[]> - comments: Types.ObjectId[] <Comment[]> - views: number
Comment(댓글) 스키마 - author: Types.ObjectId <User> - content: string - parent: { type: string id: Types.ObjectId <Feed | Comment> } - ancestor?: { type: string id: Types.ObjectId <Feed> } - recomments: Types.ObjectId[] <Comment>
하지만 해당 설계를 변경해야겠다고 생각했다.
- 한 번의 요청이 무거워진다면, 화면에 렌더링될 때까지 유저는 그저 기다려야 한다.
- 댓글의 작성으로 업데이트할 때고 게시글 전체 받아 업데이트 해야한다.
위와 같은 이유로 feed에서 comments를 populate를 하지않고, 요청을 나눠 2번나누어 요청하는 것으로 해결했다.
7. 게시글쓰기 중 화면을 벗어난다면?
게시글을 작성하다가 글쓰기 페이지를 뒤로가기 버튼이나, 로고를 버튼을 눌러 벗어난다면 작성 내용은 어떻게 관리하는 게 좋을까?
두가지 관점에서 생각해 볼 수 있다.
- 글을 쓰다가 작성을 멈추고 싶어서 일부러 벗어난 경우 (글의 내용이 없어져야 함)
- 실수로 벗어난 경우 (글의 내용이 남아져 있어야 함)
그러다 react-router-dom에 prompt라는 컴포넌트를 알게되었다.
<Prompt when={shouldConfirm} message="Are you sure you want to leave?" />
porompt는 내가 지정한 페이지이동 뿐 아니라 페이지이동을 감지하고, 한번 더 확인하므로 위의 문제를 해결할 수 있다고 생각했다.
react-router-dom v6 부터 prompt컴포넌트 기능의 불안정으로 아직 출시되지 않았다고 한다. ㅠ
그렇게 다른 방법으로는 localStorage에 내용을 저장하는 방법을 알게되었는데,
게시글에 업로드 이미지를 base64형태로 받기 때문에 어려운 상황이었다.그러던 중 useEffect의 cleanUp함수를 사용을 떠올렸다.
어차피 이 페이지를 떠난다는 건 컴포넌트들도 사라지는 의미이기 때문에 내가 지정한 취소버튼을 통한 페이지이동 뿐 아니라 페이지를 벗어날 때도 똑같이 처리 할 수 있다.실수로 페이지를 벗었을 때에 대한 제어가 안되어 아쉽지만, 내용이 남아있는 게 더 사용자 경험에서 떨어진다고 생각해 페이지 이동 시 작성내용 초기화가 되도록 하여 통일성을 만들었다.
7. useMemo, useCallback을 잘사용하는 것
게시글 리스트들을 서버에서 받아오고 데이터를 useMemo로 memoization했다.
해당 리스트에는 아이템마다 이미지가 포함될 수도 있는데, 아이템의 갯수가 많아진다면 데이터가 굉장히 무거워질 것이라 생각했다.
다른 컴포넌트로 인한 렌더링으로 할당이 반복된다면 성능저하를 가져올 수 있다 생각해 useMemo를 사용했다.
- memoization은 오히려 컴포넌트의 성능 저하를 가져올 수 있으므로 주의가 필요하다.
1. 한글 입력시 keydown 이벤트 중복 발생 현상
일부 팀원의 환경에서 엔터로 댓글 입력 시 API요청이 2번씩 들어가는 현상이 발생하였다.
하지만, 이러한 문제는 크롬 브라우저에서 한글을 사용하는 경우에만 문제가 발생한다. (영어로 입력하면 키 이벤트가 중복으로 발생하지 않음)원인
- IME composition = 한글, 일본어, 중국어 등을 변환하는 과정(composition)에서 keydown 이벤트는 OS 뿐만 아니라 브라우저에서도 처리되기 때문에 중복 발생된다.
- JavaScript의 keyboard event 객체에
isComposing
이라는 메소드가 있어 해당 메소드를 이용해 해결 가능const keyboardEventHandler = (event) => { if(event.isComposing) return; // ... 나머지 키보드 이벤트 }
- 하지만 React의 keyboard event엔
isComposing
이라는 메소드가 없다.
- !! 추가
프로젝트가 끝난 후에도 더 나은 코드로 남기고 싶어 찾아보다
nativeEvent엔 isComposing이 포함되어 있어 사용해서 문제 해결이 가능하다.onKeyDown={(e) => { if (e.key === 'Enter' && e.nativeEvent.isComposing === false) { RegisterComment(); } }}
- React에서 composition event를 별도로 제공
onCompositionStart={() => setIsComposing(true)} onCompositionEnd={() => setIsComposing(false)} onKeyUp={(e) => { if (isComposing) return; if (e.key === 'Enter') { RegisterComment(); }
- 하지만 기능이 제대로 되지 않아, 시간 부족으로 일단 keypress로 변경하여 임시로 조치
- keypress의 사용은 지양된다. (Deprecated)
- 대부분의 브라우저에서는 keydown 이벤트와 keyup 이벤트를 사용하는 것을 권장하고 있다.
- ASCII 문자에 대한 키 코드를 반환하며, 특수 문자나 조합된 키에 대한 정보를 제공하지 않는다.
첫 번째 프로젝트보다 인원도 많아졌고, 그래도 경험은 경험이였는지
조금 더 수월하게 진행되었다 생각한다.
리액트와 리덕스를 사용하며, 상태관리 집중화가 왜 생겼는지 느낄 수 있었다.
개인 프로젝트보다 규모가 커지고, 페이지도 많아지다 보니 상태관리 집중화가 되어 관리하기에 편했다.
하지만 너무 리덕스에 의존하여 대부분의 상태들이 store에 몰린 것 같다.
store에 가야할 상태와 굳이 가지 않아도 될 상태를 구분할 수 있는 개념을 익혀야겠다.
그리고 라이브러리를 쓰기 위해서도 정확히 알아야 잘 쓸 수 있다고 생각이 들어 공부를 하면 할 수록 배울 것이 많다고 느낀다.