-
Notifications
You must be signed in to change notification settings - Fork 9
[3주차] 김영우 미션 제출합니다. #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: sean
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
영우님, 미션 진행하느라 정말 많이 수고하셨습니다.
이렇게나 단일 PR에 코드 리뷰를 꼼꼼히 많이 달아본 적은 처음이네요.
작성해주신 코드 꼼꼼하게 읽어보면서 영우님께서 코드의 구조화와 품질에 정말 많은 고민과 시간을 쏟아부었다는 느낌이 들었습니다. 이러한 꼼꼼함과 성실성이 개발자에게는 반드시 필요한 부분이지 않을까 생각합니다. 이런 모습은 저도 정말 배우고 닮고 싶은 부분입니다.
특히나 JSDoc를 직접 코드에 일일히 작성해주신 것도 정말 대단하신 것 같습니다 ㅎㅎ.
제가 이 코드 리뷰를 통해서 가장 제안을 드리고 싶은 점은 바로 체계성과 유연성 사이의 균형이라는 것을 말씀드리고 싶습니다. 체계적이고 이론적으로 아름다운 코드를 작성하느냐, 아니면 좀 더 실용주의적이고 간결한 코드를 작성할 것이냐, 그 사이에서의 균형을 잡는 것이 가장 중요하다고 생각합니다.
이런 부분들만 조금 더 신경써보면 좋을 것 같습니다 ㅎㅎ (상세한 제안점은 리뷰에 자세히 남겨놓았습니다)
- 일관성 있고 정확한 네이밍
- 통일성 있는 코드 구조(styled-components vs. plain css)
- 함수의 길이 짧게 유지하기
- 불필요한 주석 빼기
- 따로 markdown으로 숲을 볼 수 있는 documentation 작성해보기
| import ChatForm from './components/ChatForm'; | ||
| import Navbar from './components/Navbar'; | ||
| import Signup from './components/Signup'; | ||
| import RequireLogin from './components/RequireLogin'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| import RequireLogin from './components/RequireLogin'; | |
| import LoginRequired from './components/LoginRequired'; |
Wrapper 컴포넌트는 형용사로 작성하는 경우가 많더라구요~!
| <Route path="/room/:id"> | ||
| <RequireLogin> | ||
| <ChatRoom /> | ||
| </RequireLogin> | ||
| </Route> | ||
| <Route path="/rooms"> | ||
| <RequireLogin> | ||
| <ChatRoomList /> | ||
| </RequireLogin> | ||
| </Route> | ||
| <Route path="/friend/:id"> | ||
| <FriendProfile /> | ||
| </Route> | ||
| <Route path="/friends"> | ||
| <RequireLogin> | ||
| <FriendList /> | ||
| </RequireLogin> | ||
| </Route> | ||
| <Route path="/settings"> | ||
| <RequireLogin> | ||
| <Settings /> | ||
| </RequireLogin> | ||
| </Route> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<RequireLogin>인 route들을 한번에 감싸보는건 어떨까요?
| <Route path="*"> | ||
| <Page404 /> | ||
| </Route> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <Route path="*"> | |
| <Page404 /> | |
| </Route> | |
| <Route path="/"> | |
| <Page404 /> | |
| </Route> |
요렇게 해도 괜찮을것같아요~!
그리고 <NotFoundPage>로 바꿔도 괜찮을듯?
| <ContactsProvider> | ||
| <RoomsProvider> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contacts, rooms등은 이제 곧 API로 쏘게 되겠죠?
그때부터는 이제 영우님이 하신 Context Provider 작업이 Redux, Apollo GraphQL, useSWR등 다양한 라이브러리들이 기본적으로 제공해준답니다 ㅎㅎ
이렇게 라이브러리를 쓰기 전 직접 손으로 Context, Provider와 State Mangement system을 구현하는 게 정말 좋은 공부인 것 같아요. 외부 라이브러리 독스를 볼때마다 아마 모든 기능 하나하나가 혁신이라고 생각하실 것 같습니다. ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오...저는 이 부분이 무슨 말인지(코드인지) 몰라서 머릿속에 물음표만 가득했는데 시원님 코멘트 보고 배워갑니다.
| // Ref to access chatcontainer (div) | ||
| const chatContainerRef = useRef(null); | ||
|
|
||
| // Scroll div to bottom when new message arrives |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
정말 딱 필요한 만큼, 필요한 위치에 주석을 적어주셨네요.
별 거 아닌 것 처럼 느껴질 수 있지만 이런 작은 주석들이 가독성을 크게 올려주는 것 같아요~
| /** | ||
| * React State users. 모든 사용자들을 저장하고 있는 User object 배열 | ||
| * @name users | ||
| * @type {Array.User} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
영우님은 앞으로 TypeScript를 정말 좋아하실거에요. 제가 장담하죠 ㅎㅎ
| initialUsers.push(new User('sean', '김영우', '미션 수행 중')); | ||
| initialUsers.push(new User('ceos.fe', '프론트', '밤 새는 중')); | ||
| initialUsers.push( | ||
| new User('ceos.sinchon', '세오스', '우리 동아리 안힘들어요^^') | ||
| ); | ||
| initialUsers.push(new User('test', '테스트', '시험용')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
밖으로 빼놓아도 될 것 같은 부분이긴 하네요~!
| * @method getCopy | ||
| * @returns {User} | ||
| */ | ||
| getCopy() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deep copy를 의도하신 것이라면 clone으로 네이밍을 하거나,
getCopy 대신 static 생성자를 만들어보는 것도 좋은 패턴인 것 같습니다. Flutter의 Dart가 이러한 패턴을 사용하고 있습니당.
static fromUser(user: User) {
return new User(this.userId, this.userName, this.statusMsg, this.lastActive);
}usage
const user1 = new User();
const user1Clone = User.fromUser(user1);| getUserById, | ||
| users, | ||
| setUsers, | ||
| createAccount, | ||
| currentUser, | ||
| selectUser, | ||
| deselectUser, | ||
| userActivity, | ||
| changeUserId, | ||
| changeUserName, | ||
| changeStatusMsg, | ||
| initializeLocalUsers, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
워후.. 굉장하군요...
Hooks에서 이렇게 많은 내용을 내려보내는게 일반적인 것 같지는 않긴 하네요.
이런 방식보다는 User 모델의 구조를 좀 바꿔서 user 모델의 메소드를 이용해서 state를 업데이트 해보는것도 나쁘지 않은 것 같습니다. 다만 백엔드 통신 대신 자체 모델을 이용한 것이니 이렇게 해보는 것도 정말 좋은 경험일 것 같습니다.
프로그래밍을 하다 무엇이든 복잡하고 많다는 느낌이 들면, 계속해서 단순화하고 쉽게 만들려고 해보는 자세가 중요한 것 같아요. 물론 어렵고 복잡하고 난해한 코드와 모듈을 이해하고 작성하는 인내력과 끈기도 필요하지만, 간단하고 효율적이면서 쉬운 코드를 지향하면서 하는 고민들이 정말 큰 밑거름이 되는 것 같습니다.
| if (typeof initialValue === 'function') { | ||
| return initialValue(); | ||
| } else { | ||
| return initialValue; | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 어떤 뜻인가요?
initialValue가 function인 경우는 어떤 경우인지 주석으로 달아주시면 또 좋을 것 같습니다.
GeonHeeAhn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
우와 영우님 코드 잘 봤습니다! 예전에 완성본을 봤을 때도 참 신경을 많이 썼구나 싶었는데 코드로 보니 더 잘 느껴지네요. 되게 사소한 부분까지도 다 신경을 쓰신것같아요. 코드 리뷰를 하며 와 이런것도 하셨군요!!라고 감탄하고 싶은 부분들이 많았는데 너무 감탄만 하고 가는 것 같아 조금 줄였습니다.ㅎㅎ 코드를 보며 제가 잘 모르는 태그/부분들이 있어서 그런 부분은 시원님이 작성해주신 코멘트 보면서 공부했습니다. 이런 갓벽한 코드를 제가 리뷰하기엔....네 더 덧붙일 말이 없어서요. 여기까지 하겠습니다. 이번 과제도 수고하셨습니다!
Docs/ContactsProvider/User.html
Outdated
| <script> prettyPrint(); </script> | ||
| <script src="scripts/linenumber.js"> </script> | ||
| </body> | ||
| </html> No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
html에서 바로 기본 틀을 짜준건가요? 중간중간에 있는 넓은 공백은 단순히 가독성을 위한 것인지 궁금합니다.
| lines[i].id = lineId; | ||
| if (lineId === anchorHash) { | ||
| lines[i].className += ' selected'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line마다 숫자를 부여한 이유가 다로 있을까요?
| <ContactsProvider> | ||
| <RoomsProvider> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오...저는 이 부분이 무슨 말인지(코드인지) 몰라서 머릿속에 물음표만 가득했는데 시원님 코멘트 보고 배워갑니다.
| value={ message } | ||
| onChange={ handleChange } | ||
| onKeyPress = { handleKeyPress } | ||
| placeholder={`${currentUser.userName} (으)로 메세지 전송`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 설정해둔거 되게 좋네요
| <SystemMessage> | ||
| {`${chat.user.userName}님이 ${ | ||
| chat.type === 'enter' ? '대화에 참여했습니다' : '' | ||
| }${chat.type === 'leave' ? '대화에서 나갔습니다.' : ''}`} | ||
| </SystemMessage> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 설정해둔 것도 너무 좋네요ㅎㅎ
| const handleCreateChatButtonClick = () => { | ||
| if (searchQuery !== '' && searchQuery !== null) { | ||
| const query = searchQuery.split(' '); | ||
| let roomName = query[0]; | ||
| let participants = []; | ||
| if (query.length > 1) { | ||
| // 검색 필드에 방이름 @사용자1 @사용자2.. 이런식으로 입력시 그 사용자가 초대된 상태로 새 방 생성 | ||
| for (let i = 1; i < query.length; i++) { | ||
| if (query[i][0] === '@') { | ||
| participants.push(query[i].slice(1)); | ||
| } else { | ||
| roomName += ' ' + query[i]; | ||
| } | ||
| } | ||
| const newRoomId = createRoom(roomName, participants); | ||
| if (newRoomId) { | ||
| setSearchQuery(''); | ||
| history.push(`/room/${newRoomId}`); | ||
| } else { | ||
| alert('새 채팅방 만들기에 실패하였습니다.'); | ||
| } | ||
| } else { | ||
| // 검색 필드에 방이름 만 입력한 경우, 현재 사용자만 추가된 상태로 방 생성 | ||
| const newRoomId = createRoom(searchQuery, [currentUser.userId]); | ||
| if (newRoomId) { | ||
| setSearchQuery(''); | ||
| history.push(`/room/${newRoomId}`); | ||
| } else { | ||
| alert('새 채팅방 만들기에 실패하였습니다.'); | ||
| } | ||
| } | ||
| } else { | ||
| // 검색 필드가 비어 있으면 새 방 이름을 입력할 수 있도록 prompt한 후 현재 사용자만 추가된 상태로 방 생성 | ||
| let newRoomName = window.prompt('새 방 이름 입력', ''); | ||
| if (newRoomName !== '' && newRoomName !== null) { | ||
| const newRoomId = createRoom(newRoomName, [currentUser.userId]); | ||
| if (newRoomId) { | ||
| history.push(`/room/${newRoomId}`); | ||
| } else { | ||
| alert('새 채팅방 만들기에 실패하였습니다.'); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 새 채팅방 만들기를 구현해보고 싶었는데 막상 하려고 보니 감이 안잡혀서 하지 못했어요. 영우님처럼 하면 되는 거였군요. 배우고 갑니다!
| const handleInviteUserClick = () => { | ||
| let userIds = window.prompt( | ||
| '초대할 사람의 @아이디 를 공백으로 구분하여 입력하세요', | ||
| '' | ||
| ); | ||
| const roomId = selectedRoom.roomId; | ||
| if (userIds !== '' && userIds !== null) { | ||
| userIds = userIds.split(' '); | ||
| userIds = userIds.map((userId) => | ||
| userId.slice(userId[0] === '@' ? 1 : 0) | ||
| ); | ||
| let failedUserIds = []; | ||
| userIds.forEach((userId) => { | ||
| if (getUserById(userId)) { | ||
| enterRoom(userId, roomId); | ||
| } else { | ||
| failedUserIds.push(userId); | ||
| } | ||
| }); | ||
| if (failedUserIds.length > 0) { | ||
| let alertString = `${failedUserIds.length.toString()}명의 사용자를 찾지 못해 초대에 실패하였습니다. (`; | ||
| for (let i = 0; i < failedUserIds.length; i++) { | ||
| alertString += '@' + failedUserIds[i]; | ||
| if (i < failedUserIds.length - 1) alertString += ', '; | ||
| } | ||
| alert(alertString + ')'); | ||
| } | ||
| userActivity(currentUser.userId); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사용자 초대 기능도 되게 색다르고 좋은 것 같아요
페이스북 메신저 컨셉으로 디자인한 메신저입니다. 친구/채팅/설정 크게 3개의 탭으로 구분되어 있으며, 로그인/회원가입 > 친구 목록 및 프로필, 채팅(1:1 및 단체방), 환경설정 (계정 이름, ID, 상태메세지 변경)으로 구성되어 있습니다.
https://react-messenger-13th-seank200.vercel.app