-
Notifications
You must be signed in to change notification settings - Fork 10
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
[4주차] 이지인 미션 제출합니다. #20
base: main
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.
아직 지인님께서 프로젝트 배포를 마무리하지 않으셔서 결과물을 보지는 못했는데, 전체적으로 recoilJS를 잘 사용하고 계신 것 같아요.
현재 코드에서 디자이너분께서 구축해주신 디자인 시스템을 styled-components
모듈에 녹여내고, app.tsx 에서 라우팅 구조를 미리 잘 생각하셔서 애플리케이션을 구현하시면 더 멋진 프로젝트가 될 것 같아요 😄
이번 과제도 정말 수고 많으셨습니다 👍
@@ -10,9 +10,13 @@ | |||
"@types/node": "^16.18.91", | |||
"@types/react": "^18.2.69", | |||
"@types/react-dom": "^18.2.22", | |||
"nodejs": "^0.0.0", |
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.
굳이 nodeJS를 따로 설치하신 이유가 있을까요??
src/App.tsx
Outdated
{isChattingRoomPage ? <ChatHead user={{ name: '', image: '' }} /> : <NavbarHead />} | ||
<div style={{ paddingTop: isChattingRoomPage ? '0px' : '80px' }}> |
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.
지인님꼐서 사용자가 chattingRoom에 입장하면, 이때에만 필요한 chatHeader
를 보여주기 위해서 app.tsx 에서 useLocation()
훅을 사용하여 조건 검사를 통해 ChatHead 컴포넌트를 보여주거나, 다른 페이지인 경우에는 NavbarHead
컴포넌트를 보여주시려고 의도하신 것 같아요!
이 방법도 괜찮은데, 저 같은 경우는 다른 분들의 프로젝트 구조를 살펴볼 때(nextJS가 아닌 reactJS의 경우), app.tsx에서는 딱 라우팅 구조만이 명시되어 있는 것을 중점적으로 살펴보곤 합니다. 따라서 세부 스타일링은 분리된 컴포넌트에서 진행해보시면 더 깔끔할 것 같아요.
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.
추가적으로 지인님께서도 이번 과제를 진행하시면서 어떤 페이지(예를 들어 페이지 a와 b라고 칠게요)들에 공통적인 UI가 존재하고, 다른 페이지들(페이지 c와 d라고 할게요)에 공통적인 UI가 존재하는 것을 한번에 "퉁쳐보고" 싶은 욕망이 드셨을 수도 있다는 생각이 듭니다. 저 같은 경우는 iphoneStatusbar
(그 아래 따라다니는 검정색 line을 의미합니다)이나, 가장 위에 위치하는 시간, 와이파이, 배터리 등을 표시하는 header
가 그랬어요.
이 경우에는 잘 사용해주신 Route 컴포넌트의 중첩과 react-router-dom
라이브러리의 Outlet
컴포넌트를 이용해보시는 걸 추천드려요. 중첩된 Route 컴포넌트 중 바깥 컴포넌트에 공통적으로 들어갈 레이아웃 컴포넌트를 넣고, 내부 컴포넌트는 페이지마다 달라지는 UI 컴포넌트를 배치하는 거죠!
제 과제코드를 첨부할게요!
해당 코드를 보시면 공통된 UI를 가져야 하는 컴포넌트는 중첩으로 되어있고, CommonLayout 컴포넌트를 보면 Outlet
컴포넌트를 활용하여 매번 바뀌는 컴포넌트를 돌려막기 하고 있어요.
제가 설명을 좀 부족하게 했을 수도 있는데, 공식 문서 참고하시면 더 좋을 것 같습니다! 💪
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 { atom } from 'recoil'; | ||
|
||
export const userProfileState = atom({ |
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.
atom()
함수를 이용하여 recoil의 전역 상태를 외부 파일에서 따로 정의해주는 것 좋은 것 같습니다
id: number; | ||
from: string; | ||
to: string; | ||
content: string; | ||
date: string; |
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.
Message 인터페이스는 useProfileState.ts 파일에서의 같은 인터페이스와 같은 속성을 지니는 것 같아요. typescript는 우리과 컴공과에서 배운 C++, Java와는 약간 다르게 duck typing이라고 구성 요소만 같으면 그냥 같은 타입인 걸로 취급해서(씨쁠쁠이나 Java는 구성 요소가 아니라 정말 선언한 타입 자체가 같아야 하는 걸로 알고 있어요), 프로젝트의 루트 디렉터리에 types 디렉터리를 생성하시고 거기에 선언하셔서 여기저기 써먹으시면 더 좋을 것 같습니다.
참고로 duck typing이란, 어원에서 알 수 있듯이 뒤뚱 뒤뚱 걷고 꽥꽥 소리를 내는 모든 것은 그냥 오리라고 처리한다는 것처럼 동일 속성을 지니는 객체의 타입이면 같은 것으로 간주하여 typescript 컴파일러가 오류를 발생시키지 않는 것을 의미합니다!
import ChatHead from './components/ChatHead'; | ||
import ChatBody from './components/ChatBody'; | ||
import ChatBottom from './components/ChatBottom'; | ||
|
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.
지인님께서도 아마 협업하신 디자이너 분께서 figma로 디자인 시스템을 만들어 주셨을텐데, 이걸 styled - Components
라이브러리를 통해 구축해보시는 것도 좋을 것 같아요.
디자이너 분들께서 웹 애플리케이션에 자주 쓰이는 색상, 폰트, 높이 등을 정하시고 이를 여기 저기에 적용하실 때, 해당 값들이 바뀌었다면 이를 사용하는 모든 컴포넌트들의 css 를 찾아다니며 수정하는 것보다는 디자인 시스템의 코드 한줄만 딱 바꿔주면 전체 적용이 되는 효과가 나거든요!
저 같은 경우는 테마 파일을 만들어두고, 이를 가장 루트 컴포넌트가 되는 app.tsx 파일에서 이와 같이 루트 컴포넌트에 감싸줘 하위 어디에서든 끌어 쓸 수 있게 했습니다.
파트장님께서 올려주셨던 자료를 보셔도 좋을 것 같아요
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 ChattingRoom = () => { | ||
const { userId } = useParams<{ userId: string }>(); | ||
console.log(useId); |
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.
불필요한 콘솔 출력문은 삭제하는 것이 좋을 것 같아용
useEffect(() => { | ||
// 로컬 스토리지에서 이전 메시지 가져오기 | ||
const storedMessages = localStorage.getItem(`messages_${userId}`); | ||
if (storedMessages) { | ||
// 이전 메시지가 있으면 파싱하여 상태에 설정 | ||
setMessages(JSON.parse(storedMessages)); | ||
} | ||
}, [userId]); // 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.
페이지가 변경될 때마다 로컬 스토리지를 조회하는 로직을 useEffect()
훅의 첫번째 콜백 인자 함수를 통해 실행하는 것도 좋은 것 같아요!
그런데 어차피 페이지가 변경되면 useEffect()
는 무조건 한번 실행되니까 의존성 배열이 그냥 []가 되어도 실행되지 않나요...? 사실 저도 이 프로젝트를 제 로컬 환경에 clone하여 실행해본게 아니라 좀 헷갈리긴 하네요! 한번 시험해보시고 괜찮으시면 알려주세요!😂
|
||
useEffect(() => { | ||
// 메시지가 추가될 때마다 스크롤을 하단으로 이동 | ||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); |
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.
optional chaining 의 깔끔한 로직 좋습니다~
const updatedMessages = [...messages, newMessage]; | ||
setMessages(updatedMessages); |
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 updatedMessages = [...messages, newMessage]; | |
setMessages(updatedMessages); | |
setMessages((prevMessages) => [...prevMessages, newMessage]); |
위와 같은 코드로도 바꿀 수 있지 않을까 싶어요!
src/pages/userList.tsx
Outdated
} | ||
|
||
const UserList = () => { | ||
const users = useRecoilValue<User[]>(userListState); |
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.
저는 매번 전역 상태 값을 가져올 때 useRecoilState()
훅만 써서 상태와, 상태를 변경하는 set뭐시기 함수를 구조 분해식으로 받아왔는데, 상태만 필요한 경우 useRecoilValue()
훅을 쓰면 더 깔끔하겠네요! 좋은 함수를 배워갑니다 :)
const ChatBody: React.FC<ChatBodyProps> = ({ messages,userImage, currentUser}) => { | ||
let lastDate = ''; | ||
let lastMinute = ''; | ||
|
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.
lastDate랑 lastMinute를 지역번수로 선언하기보다는 useState를 사용해서 컴포넌트 상태로 관리되도록 하는 것이 더 좋을 것 같아요 :)
break; | ||
default: | ||
navText = ''; | ||
} |
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.
switch문으로 경로에 따른 text값을 한방에 정의하는 방법 좋은 것 같아요!
저는 일일이 컴포넌트를 만들어서 했었는데.. 이렇게 하면 되게 간편할 것 같아요 다음번에 적용해보겠습니다 👍
src/state/chatListState.ts
Outdated
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.
사용되고 있지 않은 상태 같아요!
public/github_logo.png
Outdated
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.
public/item/profileFalse.png
Outdated
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 NavButton = styled.img` | ||
position: fixed; |
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.
fixed
로 네비게이션 바의 위치를 고정시키기보다는 NavbarContainer
내에서 아이템 위치를 조정하는게 더 좋을 것 같아요
position: fixed; | ||
font-size: 20px; | ||
width: 375px; | ||
bottom: 300px; |
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.
display: inline-block; | ||
padding: 8px 9px 8px 9px; | ||
|
||
width: 196px; |
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.
ChatBody
컴포넌트에 고정된 너비를 적용하는 것보다 텍스트 길이에 맞게 채팅 메세지 컨테이너(?)의 너비가 조정되는게 더 좋을 것 같아요!
긴 대화를 입력했을 때는 문제가 되지 않지만, 짧은 대화를 입력했을 때 남는 공간이 너무 많아요!
`; | ||
|
||
const SearchInput = styled.input` | ||
background-image: url('/item/searchbar.png'); |
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.
input 칸 내부에 있는 text는 이미지를 따와서 배경으로 적용하는 방법 말고
placeholder="Search"
속성을 input 컴포넌트에 적용하는 방법이 있어요!
<> | ||
<SearchInput | ||
type='text' | ||
placeholder='' |
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.
여기에 placeholder='Search'
이렇게 지정해주면 돼요!
padding: 10px 43px; | ||
margin-bottom: 0px; | ||
margin-left: 5px; | ||
border: none; |
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.
🥳 배포 링크
https://react-messenger-19th-tskd.vercel.app/userlist
⚙️ 구현 기능
생각보다 시간이 오래 걸리는 기능인데, 투자할 시간이 부족해 아직 구현을 완성하지 못했습니다..!
현재까지 라우팅 구현 완료한 상태이고, 메시지를 유저별로 recoil로 상태관리하는 부분, 디자인 적용 부분을 보완해야 합니다.
신속하게 완성하겠습니다.
✅ Key Questions
Routing이란?
라우팅은 사용자가 요청한 URL에 따라 해당하는 페이지를 보여주는 것을 의미합니다.
리액트에서는 react-router-dom을 사용하여 라우팅을 구현할 수 있습니다.
이를 통해 SPA(Single Page Application)의 장점인 새로고침 없는 페이지 전환을 실현할 수 있습니다.
SPA란?
SPA는 Single Page Application의 약자로, 한 개의 HTML 문서를 기반으로 동적으로 페이지를 업데이트하는 어플리케이션입니다.
리액트 앱이 SPA로 동작할 때는 페이지가 변경되더라도 전체 페이지를 다시 로드하지 않고 필요한 부분만 업데이트할 수 있습니다.
vs SPA와 대비되는 개념: MPA(Multi-Page Application)
MPA는 "Multi-Page Application"의 약자로, 여러 개의 페이지로 구성된 전통적인 웹 애플리케이션을 말합니다.
사용자가 다른 페이지로 이동할 때마다 서버로부터 새로운 HTML을 받아와 전체 페이지를 새로 고칩니다.
각 페이지 간의 전환이 발생할 때마다 서버로부터 리소스를 다시 요청하고 페이지를 다시 렌더링해야 합니다.
상태관리란?
상태관리란 리액트 애플리케이션에서 상태를 효율적으로 관리하는 것을 의미합니다.
useState를 사용하여 컴포넌트 내에서 상태를 관리할 수 있지만, 여러 컴포넌트 간에 상태를 공유하거나 복잡한 상태 관리를 위해서는 전역 상태 관리 라이브러리를 사용합니다. 대표적으로 Redux, Zustand, Recoil 등등이 있습니다.
recoil을 통한 상태관리
Recoil은 Facebook에서 개발한 상태 관리 라이브러리입니다. atom, selector를 통해 애플리케이션 내에서 상태를 효율적으로 관리할 수 있습니다.
이번 과제에서 Recoil을 통해 사용자 정보, 메시지 기록 및 채팅 목록과 같은 다양한 상태를 효율적으로 관리하고자 했습니다.(아직 완성은 아닙니다 ㅠㅠㅠ)
Recoil을 사용하면 컴포넌트가 atom을 통해 상태 값에 접근해, 이를 업데이트할 수 있습니다. 채팅방 컴포넌트에서 메시지 상태를 관리하기 위해 useRecoilState를 사용해 유저별 메시지를 관리할 수 있습니다.
이번 과제에서와 같이 메신저 앱을 만들 때, 라우팅을 사용하여 여러 페이지 간의 전환을 관리할 수 있습니다.
예를 들어, 채팅방 목록을 보여주는 페이지와 실제 채팅을 진행하는 페이지 등을 다른 URL에 맵핑하여 사용자 경로를 관리할 수 있습니다.
또한, 메시지의 상태를 관리하기 위해 전역 상태 관리 라이브러리를 사용할 수 있습니다. 이번 과제에선 처음으로 Recoil을 사용해보았습니다.
이를 통해 채팅 메시지의 상태를 공유하고, 여러 컴포넌트 간에 데이터를 효율적으로 공유해 사용할 수 있습니다.