[3주차] 김선종 과제 제출합니다.#15
Conversation
yehey-1030
left a comment
There was a problem hiding this comment.
저는 선종님 코드 리뷰하려면 아직 멀었나봐요. 앞으로 더더 열심히 해야겠어요ㅎㅎ.. local storage 기능이 제일 흥미롭고 저도 다시 도전해봐야하는 부분이라고 생각했어요. 좋은 코드 감사합니당 .
| function NavBar() { | ||
| return ( | ||
| <Fragment> | ||
| <Link to="/">Friends</Link> | ||
| <Link to="/chatlist">Chats</Link> | ||
| <Link to="/settings">Settings</Link> | ||
| </Fragment> |
There was a problem hiding this comment.
Fragment를 이용하면 의미없는 div를 사용하지 않는다는 장점이 있네요! 검색해서 찾아봤습니다 ㅎㅎ 저도 다음에는 써봐야겠어요
| [ | ||
| { | ||
| "friendId": 1, | ||
| "chats": [ | ||
| { | ||
| "senderId": 1, | ||
| "message": "안녕하세요~ 준이에요." | ||
| }, | ||
| { | ||
| "senderId": 0, | ||
| "message": "하핫,, 네 안녕하세요" | ||
| }, | ||
| { | ||
| "senderId": 1, | ||
| "message": "어? 이쁘다." | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "friendId": 4, | ||
| "chats": [ | ||
| { | ||
| "senderId": 4, | ||
| "message": "내가 말이여 지난주에 산을 탔는데" | ||
| }, | ||
| { | ||
| "senderId": 4, | ||
| "message": "아이고 이거 너무 힘들더만 아주~" | ||
| }, | ||
| { | ||
| "senderId": 0, | ||
| "message": "아 그랬구나,,," | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "friendId": 2, | ||
| "chats": [ | ||
| { | ||
| "senderId": 2, | ||
| "message": "임플란티드 키드입니다. 스껄쓰껄" | ||
| }, | ||
| { | ||
| "senderId": 2, | ||
| "message": "우리 둘이 만들어내는 변곡점들이 권력의 다툼으로 점철될거라고" | ||
| }, | ||
| { | ||
| "senderId": 2, | ||
| "message": "아 진짜 매우 킹받네" | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "friendId": 3, | ||
| "chats": [ | ||
| { | ||
| "senderId": 3, | ||
| "message": "어 왔냐?" | ||
| }, | ||
| { | ||
| "senderId": 3, | ||
| "message": "민수야 왔으면 인사를 하라고" | ||
| }, | ||
| { | ||
| "senderId": 0, | ||
| "message": "안녕하세요!" | ||
| }, | ||
| { | ||
| "senderId": 3, | ||
| "message": "그뢥" | ||
| } | ||
| ] | ||
| } | ||
| ] |
There was a problem hiding this comment.
와 진짜...이거 보고 솔직히 감탄했어용....나는....너무 귀찮...아서 안했.....는데 대박..선종님의 의지에 감탄하고 갑니다..
|
|
||
| const handleHeaderClick = () => { | ||
| console.log(currentFriend); | ||
| console.log(currentChatList.chats); |
There was a problem hiding this comment.
console.log 지우는거 깜빡하셨네요! 완벽한 선종님 실수, 인간미를 보여주기 위함인걸로 알겠습니다. ㅎㅎ
| const [currentChattingRoom, setCurrentChattingRoom] = useState( | ||
| chatroomOnLocalstorage.map((item) => { | ||
| return ( | ||
| <Link to={`/chatlist/${item.friendId}`}> | ||
| <div>{props.friends[item.friendId - 1].name}</div> | ||
| <div>{item.chats[item.chats.length - 1].message}</div> | ||
| </Link> | ||
| ); | ||
| }) | ||
| ); |
There was a problem hiding this comment.
제가 아직 react랑 js가 익숙하지 않아서 인지 이부분 어떤 역할을 하는건지 잘 모르겠어요..! 이게 채팅을 Link 태그로 묶은 컴포넌트들로 저장하는 과정인가요? Hook을 컴포넌트 배열?로 초기화할 수 도 있군요..! 틀리게 이해한 부분있으면 알려주세요!!
There was a problem hiding this comment.
맞습니다! 채팅 목록을 Link 태그로 묶어서 하나의 state로 만들어 봤어요. 근데 이거 다른 커밋에서는 고친거라 아마 내용이 달라졌을겁니다..하하!
| const FilteredChatList = (props) => { | ||
| return props.filteredList.map((item) => { | ||
| return ( | ||
| <Link to={`/chatlist/${item.id}`}> |
There was a problem hiding this comment.
${} 의 역할은 뭔가용?? 그냥 url에 파라미터? 추가하는건가요?!
There was a problem hiding this comment.
Template literal이라는 es6 문법입니다! 파이썬에서 쓰는 f-string이랑 대충 비슷한겁니다.
sebastianrcnt
left a comment
There was a problem hiding this comment.
선종님 안녕하세요,
원래도 잘 하셨지만, 매주 미션이 나갈때마다 점점 좋은 코드를 작성해주시는게 보여서 정말 뿌듯합니다. 또 선종님 특유의 센스가 돋보이는 컨텐츠까지.. 언제나 보는 맛이 있는 것 같습니다. 레반의 미래가 밝군요^^
전반적으로 코드를 효율적이고 간결하게 작성해주신 부분이 너무 좋은 것 같습니다. 앞으로 리액트를 하시면서 익히시면 좋을 만한 자잘한 팁들, 더 좋은 구조에 대한 제안점들을 리뷰에 달아 드렸습니다
| /* | ||
| 페이지를 처음 로딩할 때, 채팅 정보가 들어있는 json 파일을 불러온다. | ||
| 이후 이 json 파일을 localStorage에 저장하여 component간 주고받는다. | ||
| */ |
| import ChatList from '../chatList/ChatList.js'; | ||
| import FriendsList from '../friendList/FriendsList.js'; | ||
| import Friends from '../data/Friends.json'; | ||
| import Chats from '../data/Chats.json'; |
There was a problem hiding this comment.
특별한 경우(클래스 이름, 컴포넌트 이름)가 아니면 변수는 소문자로 시작하는 경우가 많습니다 ㅎㅎ
| import Chats from '../data/Chats.json'; | |
| import chatData from '../data/Chats.json'; |
| import React, { Fragment } from 'react'; | ||
| import styled from 'styled-components'; | ||
| import { Link } from 'react-router-dom'; | ||
|
|
||
| const NavBarContainer = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| width: 60px; | ||
| height: 100%; | ||
| padding-top: 70px; | ||
| background-color: lightgray; | ||
| box-sizing: border-box; | ||
| `; | ||
|
|
||
| const IconBase = styled.img` | ||
| width: 30px; | ||
| height: 30px; | ||
| margin: 15px; | ||
| `; | ||
|
|
||
| function NavBar() { | ||
| return ( | ||
| <NavBarContainer> | ||
| <Link to="/"> | ||
| <IconBase | ||
| src={process.env.PUBLIC_URL + '/images/user.png'} | ||
| alt="users" | ||
| /> | ||
| </Link> | ||
| <Link to="/chatlist"> | ||
| <IconBase | ||
| src={process.env.PUBLIC_URL + '/images/chat.png'} | ||
| alt="chats" | ||
| /> | ||
| </Link> | ||
| <Link to="/settings"> | ||
| <IconBase src={process.env.PUBLIC_URL + '/images/setting.png'} /> | ||
| </Link> | ||
| </NavBarContainer> | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
보통은 파일의 주 컴포넌트를 맨 위에 작성하고, styled-components를 그 아래에 작성하는 패턴이 많더라구요.
아마 파일을 열면 컴포넌트 객체가 가장 위에 있어서 보기 편해서 그런 것 같아요
| const SearchBar = (props) => { | ||
| return ( | ||
| <StyledInputContainer> | ||
| <StyledInputBox | ||
| placeholder="검색하기" | ||
| onChange={props.onInputChange} | ||
| value={props.value} | ||
| /> | ||
| <InputBoxClear onClick={props.onInputReset}>✕</InputBoxClear> | ||
| </StyledInputContainer> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
컴포넌트가 어떤 props를 가지는지에 대해서 정리해주면 더 좋을 것 같아요.
맨 첫줄을
const SearchBar = ({value, onInputReset, onInputChange}) => {혹은
| const SearchBar = (props) => { | |
| return ( | |
| <StyledInputContainer> | |
| <StyledInputBox | |
| placeholder="검색하기" | |
| onChange={props.onInputChange} | |
| value={props.value} | |
| /> | |
| <InputBoxClear onClick={props.onInputReset}>✕</InputBoxClear> | |
| </StyledInputContainer> | |
| ); | |
| }; | |
| const SearchBar = (props) => { | |
| const { onInputchange, onInputReset, value } = props; // 이렇게 따로 명시 | |
| return ( | |
| <StyledInputContainer> | |
| <StyledInputBox | |
| placeholder="검색하기" | |
| onChange={props.onInputChange} | |
| value={props.value} | |
| /> | |
| <InputBoxClear onClick={props.onInputReset}>✕</InputBoxClear> | |
| </StyledInputContainer> | |
| ); | |
| }; |
이렇게 해놓고 컴포넌트 객체의 위치를 파일 상단으로 올리면 파일을 열자마자 컴포넌트의 기능은 뭔지, 어떤 props를 받는지 한번에 볼 수 있겠죠?
| const [currentChattingRoom, setCurrentChattingRoom] = useState( | ||
| // Chats.json은 대화 상대방의 id만 가지고 있기 때문에, | ||
| // Friends.json에서 프로필 이미지를 가져온다. | ||
| chatroomOnLocalstorage.map((item) => { | ||
| const profileImage = props.friends.find((elem) => { | ||
| return elem.id === item.friendId; | ||
| }).profileImage; | ||
|
|
||
| return { | ||
| id: item.friendId, | ||
| name: props.friends[item.friendId - 1].name, | ||
| lastMessage: item.chats[item.chats.length - 1].message, | ||
| profileImage: profileImage, | ||
| }; | ||
| }) | ||
| ); |
There was a problem hiding this comment.
localStorage를 다루는 부분은 나중에 custom hook을 만들어서 해보면 또 좋을 것 같네요!
useLocalStorage() 처럼요~!
실제로 라이브러리로 만들어놓은 것도 있네용
https://usehooks.com/useLocalStorage/
| const user = { | ||
| you: { | ||
| id: 0, | ||
| name: 'Elon Musk', | ||
| profileImage: process.env.PUBLIC_URL + 'images/musk.jpeg', | ||
| id: currentFriend.id, | ||
| name: currentFriend.name, | ||
| profileImage: | ||
| process.env.PUBLIC_URL + '/images/' + currentFriend.profileImage, | ||
| }, | ||
| me: { | ||
| id: 1, | ||
| id: 0, | ||
| name: 'Seon-Jong Kim', | ||
| profileImage: process.env.PUBLIC_URL + 'images/sj.png', | ||
| profileImage: process.env.PUBLIC_URL + '/images/sj.png', | ||
| }, | ||
| }; |
There was a problem hiding this comment.
이 부분은 굳이 rendering context 내에 넣을 필요는 없을 것 같아요.
밖으로 빼셔도 괜찮을듯?
| return item.id === parseInt(friendId); | ||
| }); | ||
|
|
||
| const { chatList, onChatListChange } = props; |
| function FriendsList(props) { | ||
| const [searchQuery, setSearchQuery] = useState(''); | ||
| const [friendList, setFriendList] = useState(props.friends); |
There was a problem hiding this comment.
역시 이부분도
const { friends } = props;이렇게 프롭에 무엇이 있는지 명시해주면 좋을 것 같아용~!
| `; | ||
|
|
||
| const FilteredFriendList = (props) => { | ||
| return props.filteredList.map((item) => { |
There was a problem hiding this comment.
여기에도 item의 내부 구조를 써주면 좋을 것 같아요
const { id, profileImage, name, statusMessage } = item;| import ChatApp from './base/ChatApp'; | ||
|
|
||
| ReactDOM.render( | ||
| <React.StrictMode> | ||
| <App /> | ||
| <ChatApp></ChatApp> | ||
| </React.StrictMode>, |
There was a problem hiding this comment.
App.js 대신에 ChatApp.js를 가져오는 구조가 인상적이네요.
다만 ChatApp.js가 base 폴더에 있는것보다 src 폴더 바로 아래에 있는게 좋을 것 같다는 생각도 드네요 ㅎㅎ
과제기간이 길었던 만큼 메신저앱의 기본 기능만큼은 확실히 보여주기 위해 노력했습니다. 12기분들 과제한것도 참고해서(물론 코드는 안보고 결과물만 참고했습니다..ㅎ) 이거저거 시도 해봤는데 결국 기본적인 기능만 넣기에도 벅찬 5주가 아니었나 싶습니다.
리액트라는 것을 2주차 과제에 처음 접해보고나서 바로 그 다음주 과제에 라우팅을 이용해서 본격적인 SPA같아 보이는 것을 만들어 보려하니 처음엔 살짝 많이 막막했습니다. SPA는 뭐고 SSR은 대체 뭔지 말이지요... 라우팅, 로컬스토리지 같은 것들에 대해 이해하는데 도움주신 프론트 여러분들 모두 감사드립니다...하하,,, 적어도 이번 과제 이후로는 state와 props에 대해서는 확실히 이해하게 된 것 같습니다. 뭐라도 남은 것 같아서 기쁘네요.
채팅방을 들어왔다 나가도 기존의 채팅 내용이 유지되는 기능과, 채팅방에서 대화를 주고받았을 때, 채팅방 목록에서 마지막으로 보낸 메세지가 미리보기로 뜨게 하는 기능을 만드는데 과제시간의 거의 대부분을 쓴것 같네요,,, 개빡치는 css는 논외로..ㅎㅎ.. 아 참고로 채팅방에서 위에 빨간 동그라미 누르면 원래 채팅목록으로 다시 돌아갑니다. 혹시 모르실까봐 말씀드립니다... 간지안나게 뒤로가기 하지말기...
코드를 모두 작성하고나서 주욱 읽어보니 주석이 없으면 도저히 이해가 불가능 할 것 같다는 결론이 들어 주석 작성에 공을 들였습니다.
제 코드 리뷰해주시는 분들 중에서 이해 안되는 부분은 꼭 저한테 갠톡으로라도 물어보세요,,, 서로 공부되고 좋잖아요...
아 그리고 이번주는 3주차 과제 아닌가여,,, 왜 다들 4주차라고,,,
https://react-messenger-13th-nf5wf6zpj-s-j-kim.vercel.app/