-
Notifications
You must be signed in to change notification settings - Fork 10
[4주차] 송아영 미션 제출합니다. #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: master
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.
멋진 과제 진행하시느라 고생 많으셨습니다.
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
dist | ||
dist-ssr | ||
*.local | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
.idea | ||
.DS_Store | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
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.
yarn 패키지 매니저를 이용해서 의존성을 관리하고 있어요. yarn 1 버전을 이용하면 node_modules에 의존성을 설치할 수 있지만 그렇지 않은 사용자들이 있으니 .yarn 도 추가해줍시다
import Frame from './components/Frame'; | ||
import Chats from './pages/ChatList'; | ||
import ChatRoom from './pages/ChatRoom'; | ||
|
||
import '@/styles/global.css'; | ||
import Home from './pages/Home'; | ||
import Profile from './pages/Profile'; | ||
|
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.
관련 ts 옵션으로 절대 경로를 만들어주신 것 같은데, @component/ 방식으로도 쓸 수 있게 더 설정하셔서 코드 더 깔끔하게 유지할 수 있을 것 같습니다
import { getRecentActiveFriends } from '@/apis/getRecentActiveFriends'; | ||
import MainTopBar from '@/components/MainTopBar'; | ||
import RecentActiveFriend from '@/components/RecentActiveFriend'; | ||
import { getAllFriends } from '@/apis/getAllFriends'; | ||
import FriendsItem from './components/FriendItem'; | ||
import DefaultProfile from '@/components/DefaultProfile'; | ||
import { getCurrentUserInfo } from '@/apis/getCurrentUserInfo'; | ||
import { getProfileColor } from '@/utils/getProfileColor'; | ||
import Divider from '@/components/Divider'; | ||
import NavBar from '@/components/NavBar'; | ||
import { useNavigate } from 'react-router-dom'; | ||
|
||
export default function Home() { | ||
const nav = useNavigate(); | ||
|
||
const friends = getAllFriends(); | ||
const recentActiveFriends = getRecentActiveFriends(); | ||
const currentUserInfo = getCurrentUserInfo(); | ||
|
||
const currentUserBgColor = getProfileColor('background', currentUserInfo?.color || 'blue'); | ||
const currentUserPathColor = getProfileColor('path', currentUserInfo?.color || 'blue'); | ||
|
||
return ( | ||
<div> | ||
<MainTopBar content="친구" /> | ||
|
||
<div className=" h-[38.5rem] overflow-scroll no-scrollbar"> | ||
<button | ||
onClick={() => nav('/profile')} | ||
className="flex w-full gap-2.5 items-center px-5 py-[1.125rem] head2-semibold | ||
active:bg-black-100 transition-colors" | ||
> | ||
<DefaultProfile isMyProfile={true} bgColor={currentUserBgColor} pathColor={currentUserPathColor} /> | ||
{currentUserInfo?.name || '알수없음'} | ||
</button> | ||
|
||
<Divider /> | ||
<div className="text-black-500 caption1-medium mx-5 mt-1 mb-2">최근 접속한 친구</div> | ||
|
||
<div className="px-5 py-2.5 flex gap-2 overflow-scroll no-scrollbar"> | ||
{recentActiveFriends.map((friend) => ( | ||
<RecentActiveFriend key={friend.id} {...friend} /> | ||
))} | ||
</div> | ||
|
||
<Divider /> | ||
<div className="text-black-500 caption1-medium mx-5 mt-1 mb-2">친구 {friends.length}</div> | ||
|
||
<div className="pb-2.5"> | ||
{friends.map((friend) => ( | ||
<FriendsItem {...friend} /> | ||
))} | ||
</div> | ||
</div> | ||
|
||
<NavBar /> | ||
</div> | ||
); | ||
} |
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.
현재 index.tsx 파일에서 컴포넌트 코드를 작성해주시고 있어요. js는 특정 디렉터리를 파일 명시 없이 import 하면 index.js를 가져오니까, 아예 Home 디렉터리 아래에 HomePage.tsx 컴포넌트에 지금 코드를 작성해두고 index.ts에서는 export * from "./Homepage."
구문만 써주면 프로젝트 내 다른 파일에서 편리하게 import할 수 있을 것 같아요.
email: string; | ||
color: Color; | ||
isActive: boolean; | ||
lastActiveAt: 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.
실제 사용되는 코드를 보면, 단순 문자열이라기보다는 Date 객체가 들어가는 것 같아요. 따라서 타입도 Date로 적용해주시는 것은 어떨까요?
export interface MessageDto { | ||
id: string; | ||
content: string; | ||
createdAt: string; | ||
fromUser: UserDto; | ||
} | ||
|
||
export interface ChatRoomDto { | ||
chatRoomId: number; | ||
chatRoomName: string | null; | ||
joinedUsers: UserDto[]; | ||
latestMessage: MessageDto; | ||
} | ||
|
||
export interface UserDto { | ||
id: number; | ||
name: string; | ||
email: string; | ||
color: Color; | ||
isActive: boolean; | ||
lastActiveAt: 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.
전체적으로 Dto 라는 이름으로 타입(인터페이스라고도 하지만요)을 만들어 주셨는데, Dto는 사실 백엔드와 프론트엔드 간에 왔다 갔다하는 데이터를 의미하는 것에 더 가깝다고 생각해요.
물론 본 과제가 실제 백엔드 연동까지 함께한다면 의도하신 것처럼 apis 디렉터리에 넣는 것은 참 좋은 생각이에요! 그런데 dto는 대체로 백엔드 애플리케이션에서 프론트로 보낼 데이터의 타입을 네이밍할 때 많이 쓴다는 것을 알아두시면 좋을 것 같습니다.
content, | ||
}: { | ||
opacity: number; | ||
Icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>; |
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.
이 작은 한 줄의 코드가 정말 중요하다는 것을 말씀드리고 싶어요. FC 같은 타입도 정말 중요하고, SVGProps 같은 타입이 굉장히 중요하거든요. 현재 에셋으로 가지고 있는 svg 파일들이 그대로 바로 react 컴포넌트로 적용되는데 어떻게 가능한지 svgr이라는 주제를 중심으로 살펴보셔도 좋습니다.
<div> | ||
<GroupProfile bgColors={bgColors} pathColors={pathColors} /> | ||
</div> |
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.
굳이 불필요하게 div 요소를 추가하기 보다는 빈 <></> 이나 </Fragment/> 내부에 컴포넌트 코드를 넣으셔도 될 것 같습니다.
import Profile from '@/assets/icons/profile.svg?react'; | ||
import clsx from 'clsx'; | ||
|
||
export default function DefaultProfile({ | ||
bgColor, | ||
pathColor, | ||
isMyProfile = false, | ||
hasBorder = true, | ||
profileImgUrl, | ||
isActive, | ||
}: { | ||
bgColor: string; | ||
pathColor: string; | ||
isMyProfile?: boolean; | ||
hasBorder?: boolean; | ||
profileImgUrl?: string; | ||
isActive?: boolean; | ||
}) { | ||
const size = isMyProfile ? 30 : 27; | ||
return ( | ||
<div | ||
className={clsx( | ||
'relative flex justify-center items-center border rounded-full', | ||
isMyProfile ? 'w-11 h-11' : 'w-9 h-9', | ||
hasBorder ? 'border-black-100' : 'border-transparent', | ||
bgColor, | ||
)} | ||
> | ||
{profileImgUrl ? <div></div> : <Profile width={size} height={size} className={pathColor} />} | ||
{isActive !== undefined && ( | ||
<div | ||
className={clsx( | ||
'absolute right-0.5 -bottom-0.5 w-2.5 h-2.5 border-2 border-black-50 rounded-full', | ||
isActive ? 'bg-main-400' : 'bg-black-400', | ||
)} | ||
></div> | ||
)} | ||
</div> | ||
); | ||
} |
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.
현재 DefaultProfile
컴포넌트 뿐만 아니라 동일 레벨의 Divider 등의 컴포넌트들은 여기저기에서 쓰이고 있죠? 아예 ui-common 이라는 디렉터리 내부에 위치시키는 것이 협업 시에 더 좋지 않을까 싶어요
} | ||
|
||
export const useMessagesStore = create<MessagesStore>()( | ||
persist( |
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.
나중에도 브라우저를 열때 데이터가 남아있도록 zustand-persist를 사용해주셨군요! 지금은 프론트엔드 솔로 프로젝트라 이 방식을 택하지만, 사실 채팅 내역은 중요한 정보잖아요. 이런 건 원래 백엔드가 디비에서 잘 관리할 일이고 인프라, 백 코드 개선 등으로 속도는 알아서 개선할 일이니 마음껏 짬을 때립시다.
return ( | ||
<div className="sticky px-5 py-2.5 grid grid-cols-[4.75rem_auto_4.75rem] items-center"> | ||
<button | ||
onClick={() => nav(-1)} |
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.
제가 잠깐 탭을 닫아버리고 주소만 복사해서 바로 다시 들어갔는데, 이때에는 History가 없잖아요. 그런 경우에는 뒤로 돌아갈 페이지가 없는 이상한 동작이 발생할 수도 있습니다. 이 조건도 검사해서 아예 버튼을 deactivate 해주거나 합시다.
배포 링크
https://react-messenger-21th-git-gustn99-gustns-projects.vercel.app/
과제를 하며...
Key Questions
1. React Router의 동적 라우팅(Dynamic Routing)이란 무엇이며, 언제 사용하나요?
2. 네트워크 속도가 느린 환경에서 사용자 경험을 개선하기 위해 사용할 수 있는 UI/UX 디자인 전략과 기술적 최적화 방법은 무엇인가요?
3. React에서 useState와 useReducer를 활용한 지역 상태 관리와 Context API 및 전역 상태 관리 라이브러리의 차이점을 설명하세요.
지역 상태 관리
전역 상태 관리