-
Notifications
You must be signed in to change notification settings - Fork 10
[4주차] 최무헌 과제 제출합니다. #13
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: main
Are you sure you want to change the base?
Conversation
dragunshin
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.
컴포넌트 분리가 깔끔해서 코드가 어떤 것을 의미하는지 보기 편했습니다 깔끔한 컴포넌트 구성을 배우고 갑니다 고생하셨습니다!
| <img src="/images/nnotch.svg" /> | ||
| <ChatHeader /> | ||
|
|
||
| <main className="!pb-[calc(84px+env(safe-area-inset-bottom))]"> |
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.
직관적인 safe area 처리같은데 css변수로 처리하면 관리에 유용할 것 같아요
| //import { shallow } from "zustand/shallow"; | ||
|
|
||
| export default function ChattingList() { | ||
| const init = useChat((s) => s.init); |
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.
useChat 여러개를 사용하는 부분에 대해서 zustand의 shallow를 사용하는 방식으로 렌더링 여러번을 줄일 수 있다고 합니다
shallow
관련된 링크라 같이 참고해 보면 좋을 것 같아요
| <span className="text-[11px] text-gray-400">{item.timeLabel}</span> | ||
| {item.unread > 0 ? ( | ||
| <span className="flex h-5 min-w-5 items-center justify-center rounded-full bg-red-500 px-1 text-[11px] font-semibold text-white"> | ||
| {item.unread > 99 ? "99+" : item.unread} |
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.
99개가 넘어가면 99+ 처리를 해놓은것이 되게 디테일한 것 같아요
| import Coupon from "@/assets/coupon.svg?react"; | ||
| import ProfileGift from "@/assets/profile/profileGift.svg?react"; | ||
|
|
||
| export default function FriendsList() { |
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.
여기도 shallow 써보면 좋을것 같아요!
| [categoryOrder, dynamicKeys] | ||
| ); | ||
|
|
||
| return ( |
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.
목록 기반이면 ul을 써보는 것도 하나의 방법같아요
| f.id === userId ? { ...f, groups: unique } : f | ||
| ), | ||
| }); | ||
| const unknown = unique.filter((g) => !get().categoryOrder.includes(g)); |
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 { friends, query, activeCollection } = get(); | ||
| const q = query.trim().toLowerCase(); | ||
|
|
||
| let list = friends.filter( |
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.
현재 검색이 name status만 가능한데 전화번호같은 다른 검색으로도 가능하면 좋을 것 같아요
chaeyoungwon
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.
과제하시느라 수고 많으셨어요! 🙌
현재 채팅 페이지 등 루트가 아닌 경로에서 새로고침 시 404 에러가 발생하는 것 같아요.
vercel.json을 추가하셔서, 지난 3주차 과제 피드백 노션에 안내된 설정을 참고해 적용해보시면 좋을 것 같습니다
다음 과제도 파이팅입니다!
| --text-headline-1: 24px; | ||
| --text-headline-2: 22px; | ||
| --text-headline-3: 20px; | ||
|
|
||
| --text-body-1: 18px; /* Semibold, 120%, -2% letter-spacing */ | ||
| --text-body-2: 18px; /* Regular/Medium, 120%, -2% */ | ||
| --text-body-3: 16px; /* Semibold, 120%, -2% */ | ||
| --text-body-4: 16px; /* Medium, 120%, -2% */ | ||
| --text-body-5: 14px; /* Semibold, 130% */ | ||
| --text-body-6: 14px; /* Medium, 130% */ | ||
|
|
||
| --text-caption: 12px; /* Regular, 130% */ |
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.
typography는 글자 크기만 지정하기보단,
@layer utilities를 활용해 폰트 크기, 굵기, 줄 간격 같은 속성까지 한 번에 정의해두면 좋습니당
컴포넌트에서 일일이 스타일 지정 안 해도 되고,
전체적으로 통일감 있는 디자인을 유지할 수 있습니다!
다른 분들 코드도 참고해보시면 좋을 거 같아요!
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.
해당 이미지를 사용하지 않고, 하나의 아이콘만 활용해서 색상을 랜덤으로 지정해주는 방식도 괜찮을 것 같아요!
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.
현재 중복된 이미지들이 꽤 포함되어 있는 것 같아요 !!
사용 중인 이미지만 남기고 불필요한 이미지는 정리해주시면 좋을 것 같습니다 :)
또한, 직접 구현 가능한 텍스트는 이미지 대신 코드로 작성해주시는 걸 추천드려요!
초기 렌더링 시 텍스트가 더 빠르게 표시될 뿐만 아니라, 텍스트를 수정하기 위해 이미지를 교체할 필요가 없고
전체 프로젝트 용량도 줄일 수 있으니까요!
| export const api = { | ||
| users: { | ||
| list: () => http<any[]>("/data/users.json"), | ||
| }, | ||
| collections: { | ||
| list: () => http<any[]>("/data/collections.json"), | ||
| }, | ||
| messages: { | ||
| list: () => http<Record<string, any[]>>("/data/messages.json"), | ||
| }, | ||
| rooms: { | ||
| list: () => http<any[]>("/data/room.json"), | ||
| }, | ||
| // messages는 localStorage 이용 | ||
| }; |
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.
any 타입 대신 실제 데이터 구조에 맞는 타입을 지정해주시면 더 좋을 거 같아요!
| const tabs: { key: ActiveKey; src: string; alt: string }[] = [ | ||
| { key: "friends", src: "/images/tab/tabFriends.svg", alt: "친구" }, | ||
| { key: "chat", src: "/images/tab/tabChatting.svg", alt: "채팅" }, | ||
| { key: "openchat", src: "/images/tab/tabOpenChat.svg", alt: "오픈채팅" }, | ||
| { key: "shopping", src: "/images/tab/tabShopping.svg", alt: "쇼핑" }, | ||
| { key: "more", src: "/images/tab/tabMore.svg", alt: "더보기" }, | ||
| ]; |
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.
constants 폴더로 분리하면 좋을 거 같아용
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 type { User } from "@/types"; | ||
| //import Avatar from "../../components/Avatar"; | ||
|
|
||
| export default function FriendsRow({ f }: { f: 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.
단순 map 콜백 내부에서는 축약형 변수를 사용해도 괜찮지만,
컴포넌트의 props로 전달되는 경우에는 의미가 명확한 변수명을 사용하는 것이 더 좋을 거 같네요!
| <p className="truncate text-[13px] text-gray-600"> | ||
| {item.lastText} | ||
| </p> |
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.
목데이터를 기반으로 마지막 텍스트를 설정해주셨는데,
사용자가 입력한 내용이 로컬 스토리지에 저장된다면,
해당 저장값을 불러와 마지막 텍스트로 표시하는 방식도 좋을 것 같아요!
| if (loading) { | ||
| return ( | ||
| <div className="mx-auto min-h-screen w-[375px] border border-gray-200 bg-white"> | ||
| <main className="divide-y divide-gray-200"> | ||
| {[...Array(6)].map((_, i) => ( | ||
| <div key={i} className="flex items-center gap-3 px-4 py-3"> | ||
| <div className="h-12 w-12 rounded-xl bg-gray-200 animate-pulse" /> | ||
| <div className="flex-1 space-y-2"> | ||
| <div className="h-3 w-1/3 bg-gray-200 animate-pulse rounded" /> | ||
| <div className="h-3 w-2/3 bg-gray-200 animate-pulse rounded" /> | ||
| </div> | ||
| <div className="h-3 w-8 bg-gray-200 animate-pulse rounded" /> | ||
| </div> | ||
| ))} | ||
| </main> | ||
| </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.
| const ref = useRef<HTMLDivElement>(null); | ||
| useEffect(() => { | ||
| const el = ref.current; | ||
| if (el) el.scrollTop = el.scrollHeight; | ||
| }, [list.length]); |
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.
흠 현재 스크롤이 맨 아래로 자동으로 내려가지 않는 같은데, 다시 한 번 확인해보시면 좋을 거 같아요!
|
|
||
| export default function App() { | ||
| return ( | ||
| <div className="mx-auto w-[375px] h-[812px] bg-white "> |
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-22nd-ecru.vercel.app/
느낀/배운점
저는 항상 shadcn같은 CSS라이브러리를 가져다 쓰는 데 익숙했지만 이번에는 직접 디테일하게 Tailwind만으로 디자인을 구현한 것이 가장 크게 배운 점인 것 같습니다.
margin, gap, 안전영역 등등 각각을 어떤 맥락에서 쓸 지 이해했으며, 부모와의 관계도 고려해야 의도치 않은 흰색 여백 같은 것들을 피할 수 있다는 것을 배웠습니다.
Key Questions
React Router의 동적 라우팅(Dynamic Routing)이란 무엇이며, 언제 사용하나요?
동적 라우팅 (Dynamic Routing)이란
페이지의 형식은 같지만, 그 내용만 바뀌는 경우에 사용합니다. 예를 들어 사용자 프로필 페이지의 URL을 /users/:userId와 같이 사용합니다.
이때 :userId 부분이 동적 파라미터(변수)이며 사용자가 /users/123로 접속하든 /users/321으로 접속하든 React Router는 동일한 UserProfile 컴포넌트를 렌더링합니다.
그리고 컴포넌트 내에서 useParams라는 Hook을 사용해 :userId 파라미터 값을 꺼내 사용할 수 있습니다.
네트워크 속도가 느린 환경에서 사용자 경험을 개선하기 위해 사용할 수 있는 UI/UX 디자인 전략과 기술적 최적화 방법은 무엇인가요?
UI/UX 디자인 전략- 스켈레톤UI
단순히 로딩 스피너를 보여주는 것은 오히려 사용자를 지치게 할 수 있습니다. 따라서 곧 보여질 것 처럼 데이터가 들어올 레이아웃을 미리 (회색) 박스 등으로 보여줍니다.
추가로, 댓글과 같은 기능은 서버에 업로드 하기전에 로컬 데이터로 이미 댓글이 달린 것처럼 보여줄 수 도 있습니다.
기술적 최적화 방법- 이미지 리사이징
아무래도 이미지가 전체 api요청에서 가장 큰 용량을 차지하고 있을 것입니다. 이때 이미지를 브라우저나 모바일 환경에 맞추어 리사이징한 이미지를 전달해준다면 이미지 크기를 효과적으로 줄일 수 있습니다.
React에서 useState와 useReducer를 활용한 지역 상태 관리와 Context API 및 전역 상태 관리 라이브러리의 차이점을 설명하세요.
"컴포넌트의 개개인 저장소"
상태가 특정 컴포넌트 내부에서만 필요하거나 부모/자식 관계처럼 1~2단계 아래로만 전달(props)될 때 사용합니다.
다른 컴포넌트와 격리되어 있어 관리가 쉽습니다.
useReducer를 사용하는 이유는 useState로 관리하기엔 상태 로직이 복잡할 때 사용합니다.
상태를 변경하는 Action을 미리 정의하고 Reducer에 따라서만 상태를 업데이트합니다.
useState보다 코드는 길어지지만 상태 변화를 예측하기 쉬워집니다.
2. 전역 상태 관리 (Context API,Zustand)
"애플리케이션의 공용 저장소"
앱의 여러 컴포넌트가 공유해야 하는 상태일때 사용합니다. 예를들어 로그인한 사용자 정보, 테마(다크/라이트 모드), 장바구니 목록등이 있습니다.
지역 상태만 쓰면, 저 멀리 떨어진 컴포넌트에 데이터를 전달하기 위해 중간의 모든 컴포넌트를 거쳐 props를 전달해야 합니다. 이를 Prop Drilling이라고 하며, 매우 비효율적인 코드가 됩니다.