-
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주차] 윤영준 과제 제출합니다. #11
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.
굿굿 최고!! 영준이 코드는 늘 보면서 배우는 거 같아! 고생했어!!
|
||
|
||
export default function ChatList() { | ||
const { users } = useChatStore(); |
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를 사용하셨네요!!
`}> | ||
<Outlet /> {/* 자식 Route 컴포넌트가 여기에서 렌더링됩니다 */} | ||
</main> | ||
{/* <NavBar /> */} |
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.
혹시 NavBar를 주석처리하신 이유가 따로 있으시ㄹ까요?
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.
저도 궁금합니다!! 🔎
<p className='text-title-2'>3</p> | ||
<p className='text-body-2-m'>posts</p> | ||
</span> | ||
<span onClick={()=>navigate("/profile/followList")} className='text-center cursor-pointer'> |
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.
혹시 button 태그를 쓰지 않고 span으로 처리한 이유가 있으신가요?
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.
음 그리고 개인적으로,
<span onClick={()=>navigate("/profile/followList")} className='text-center cursor-pointer'> <p className='text-title-2'>1000</p> <p className='text-body-2-m'>following</p> </span>
이 부분이 계속 반복이 되는 것 같아요!
저라면, 요 부분을 따로 빼서 중복을 줄일 것 같습니다!
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를 습관적으로 써서 지금 div 밭이라 얼른 수정해야 할 것 같네용... 매번 까먹는 시멘틱 태그지만 기본적인 거일 수록 항상 중요한 것 같아용... 🥹
<span onClick={()=>navigate("/profile/followList")} className='text-center cursor-pointer'> | ||
<p className='text-title-2'>1000</p> | ||
<p className='text-body-2-m'>following</p> | ||
</span> |
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.
<span onClick={()=>navigate("/profile/followList")} className='text-center cursor-pointer'> | |
<p className='text-title-2'>1000</p> | |
<p className='text-body-2-m'>following</p> | |
</span> | |
//겹치는 부분 | |
function Stat({ count, label, onClick }) { | |
return ( | |
<span onClick={onClick} className='text-center cursor-pointer'> | |
<p className='text-title-2'>{count}</p> | |
<p className='text-body-2-m'>{label}</p> | |
</span> | |
); | |
} | |
<div className='w-[228px] h-[40px] inline-flex gap-8'> | |
//겹치는 부분에서의 사용 | |
<Stat count="3" label="posts" /> | |
<Stat count="1000" label="followers" onClick={() => navigate("/profile/followList")} /> | |
<Stat count="1000" label="following" onClick={() => navigate("/profile/followList")} /> | |
</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.
이렇게 말이에요!! 저는 개인적으로 겹치는 컴포넌트 (그게 디자인이든 로직이든 관계없이)가 있으면 일단 최대한 공통으로 묶을 수 있는지부터 생각하는 편이라서 요렇게 제안드려 볼게요!
className={`w-[186px] flex justify-center items-center px-0 py-2 cursor-pointer | ||
${activeTab === 1 ? 'text-black border-b-2 border-black' : 'text-gray500'}`}> |
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.
tailwindcss에서는 조건부를 이렇게 쓰는거군요..... 사실 제 스타일이 아니라는 이유로 깊게 공부하진 않았어서 요거 배우고 갑니당ㅎㅎㅎㅎ
const handleTabClick = (tabIndex: number) => { | ||
setActiveTab(tabIndex); | ||
}; | ||
const handleRemove = (username: 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.
콘솔로 되어있는데 그냥 테스트용 함수였다면 지우ㅓ주시는 건 어떨까요?
<div className="w-full h-full flex flex-col items-center justify-center"> | ||
<p className='text-body-2-m text-gray500'>{tabs[1].text}</p> | ||
<p className='text-body-2-m text-gray500'>{tabs[1].count}명</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.
<div className="w-full h-full flex flex-col items-center justify-center"> | |
<p className='text-body-2-m text-gray500'>{tabs[1].text}</p> | |
<p className='text-body-2-m text-gray500'>{tabs[1].count}명</p> | |
function HowManyPeople({tabs){ | |
<div className="w-full h-full flex flex-col items-center justify-center"> | |
<p className='text-body-2-m text-gray500'>{tabs[{tabs}].text}</p> | |
<p className='text-body-2-m text-gray500'>{tabs[tabs].count}명</p> | |
</div> | |
[1,2].map((data) => { | |
어쩌구... | |
} |
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 (userChange) { | ||
currentUser = { | ||
user_id: 5, | ||
userName: 's.ol_lala', | ||
displayName: 'minsol', | ||
profileImage: require('../../assets/Image/profile.jpg'), | ||
posts: 0, | ||
followers: 1000, | ||
following: 1000, | ||
} as 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.
요부분 set함수로 바꾸는게 더 직관적이지 않을까 싶습니다!
{/* ChatRoom User Description */} | ||
<div className={`flex flex-col items-center gap-3 pt-8 px-12`}> | ||
<div className='w-[279px] flex flex-col items-center gap-2'> | ||
<img src={currentUser?.profileImage} alt="Profile" className="w-[96px] h-[96px] rounded-full cursor-pointer"/> | ||
<p className='h-[21px] text-center text-title-2 text-black'>{currentUser?.userName}</p> | ||
<span className='w-full text-body-2-m text-gray500 text-center self-stretch'> | ||
<p>{currentUser?.displayName}</p> | ||
<p>{currentUser?.followers} followers · {currentUser?.posts} posts</p> | ||
<p>You don’t follow each other on Instagram</p> | ||
</span> | ||
</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 useChatStore = create<ChatState>()( | ||
persist( | ||
(set, get) => ({ | ||
users: [ |
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.
따로 constant에서 따로 선언해서 불러와도 될 거 같아요!
"typescript": "^4.9.5", | ||
"web-vitals": "^2.1.4" | ||
"web-vitals": "^2.1.4", | ||
"zustand": "^5.0.0-rc.2" |
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.
헉!! 전역 상태 관리에 zustatnd를 사용하셨군요... 뭔가 제가 다뤄보지 않은 기술을 사용하시는 걸 보면 항상 멋있는 것 같습니다... 👍🏻 그동안 Context API랑 대체 뭔 차이인지 아리송한 부분이 있었는데 전체적인 개념은 비슷할지언정 리렌더링 측면에서 훨씬 효율적이네요... 가볍기두 하고용... 반드시 공부해서 사용해 봐야겠습니다 여담이지만... 항상 이른 순서로 과제 제출하시는 게 참... 대단한 것 같습니다 🔥
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 라이브러리는 카토 다이시라는 일본인 프로그래머가 만든 전역 상태 관리 라이브러리로, 이 분께서 jotai 같은 라이브러리도 만드셨어요!
해당 라이브러리는 전역 상태를 Javascript의 클로저 개념을 이용해 구현하기 때문에 기존의 리액트 컴포넌트 트리를 따라가지 않는다는 장점이 있어요. 민재 말처럼 contextAPI를 사용할때에는 연관 없는 상태 변화를 통한 불필요한 리렌더링을 막아주기 위해 개발자가 일일히 직접 말해줘야하는데, zustand는 store의 개념을 활용해 redux 처럼 단방향의 flux 패턴을 보이고 있어요(디버깅이 더 편하겠죠?). 기존의 redux보다 boilerplate 코드가 확실히 적고, redux dev tools까지 사용할 수 있다는 점에서 인기가 많은 라이브러리입니다. contextAPI와 어떤 차이가 있는지 더 찾아보시고 공유 많이 해주세요 💪🏼
"react-scripts": "5.0.1", | ||
"react-window": "^1.8.10", |
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.
우왁 처음 보는 라이브러리이네용... 이름만 보고 생각했을 때는 윈도우? 반응형인가 싶긴 했는데 찾아보니 메모리 사용량 측에서 매우 최적화 시킬 수 있는 라이브러리이군요!!! 이게 이번 key-question 2번의 대한 답을 찾다가 lazy-loading 기법과 어떤 차이인 건지 이것도 약간 아리송 ㅎㅎ 했었는데 DOM에 요소가 계속해서 쌓이고 안 쌓이고의 차이라 메모리 성능 최적화를 위해서는 window을 사용한다고 이해했습니다. 이미 알고 계실지 모르겠지만 react-window를 찾아보다 보니 react-virtualized라는 것도 찾아볼 수 있었는데 window와 비교해 알아보니 각각의 장단점을 훨씬 더 잘 이해할 수 있었어용 아직 모르신다면 찾아보시는 걸 추천 드리며...... ✨
if (messagesEndRef.current) { | ||
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }); | ||
} | ||
}, 500); // 100ms의 짧은 지연 시간 설정 |
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.
오!! 영준 님의 스크롤이 뭐랄까 되게 부드럽게 내려간다고 생각했는데 지연 시간을 짧게 설정해서 가능했던 거군요 ㅎㅎ UX가 훨씬 개선되는 것 같아요 👍🏻
))} | ||
</div> | ||
{/* 스크롤 이동을 위한 빈 div */} | ||
<div ref={messagesEndRef} /> |
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를 참조하게 해서 useEffect로 새로운 메세지가 입력될 때마다 자동으로 스크롤되게 구현하셨군요! 이런 방법도 참고해 봐야겠습니다 ㅎㅎ 저는 chatRef.current.scrollTop = chatRef.current.scrollHeight;
이렇게 scrollTop 속성으로 스크롤의 현재 위치를 나타내게 하고, 이 값을 scrollHeight로 설정해서 스크롤이 가장 아래로 이동하게 했습니다!
|
||
|
||
// 현재 유저의 메시지를 zustand로부터 가져옴 | ||
const messages = currentUser ? getUserMessages(currentUser.user_id) : []; |
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.
currentUser를 users 배열에서 찾은 후, userChange 상태에 따라 덮어쓰는 로직이 한 곳에서 함께 처리되는 이 부분을 아래 혜인 언니의 말처럼 set 함수를 사용해 상태를 업데이트하여 ChatRoom 컴포넌트에서 상태를 직접 관리하는 대신, Zustand 스토어에서 처리하게 하는 거 어떨까용?!!! 🔥
let newValue = inputValue; | ||
|
||
if (key === 'backspace') { | ||
newValue = inputValue.slice(0, -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.
newValue를 수동으로 업데이트하는 방식 말고 setInputValue 함수의 인자로 이전 값을 기반으로 새로운 값을 반환하는 함수를 전달하는 건 어떨까용?!?! if (key === 'backspace') { setInputValue(prev => prev.slice(0, -1)); } else if (key === 'return') { handleSendMessage(); setInputValue('');...
이런 식으로... 🔥
@@ -0,0 +1,70 @@ | |||
import { useState } from 'react'; |
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.
저도 키보드 올라오는 거 구현하고 싶었는데......... 끝내 후순위가 돼서 ㅜㅜ... 디자이너 님께서 자꾸 안 해도 된다고 토닥여주셔서 넘겼는데 영준 님 UI 보니 저도 구현해 봐야겠네요... 🥹🔥👍🏻
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.
@mimizae 지켜볼겁니다.
<p | ||
className={`max-w-[157px] text-body-2-b truncate | ||
${user.isActivated ? 'text-gray600' : 'text-gray500'}`} | ||
>{user.lastMessage ? user.lastMessage : 'No messages yet'}</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.
사용자가 새로운 채팅을 보내면 그 새로운 채팅을 기반으로 이 ChatList의 마지막 메세지도 업데이트 되도록 리팩토링해 보시는 건 어떨까용??
지금은 제가 어떤 채팅을 보내고 ChatList 페이지로 이동해서 새로고침해도 그대로입니다 🥹 저는 recoil을 썼고 두 사용자의 대화가 업데이트 될 때마다 atom에 바로 저장해서 ChatList 페이지가 로딩될 때 맨 마지막 채팅을 불러오게 했습니다......
저는 사실 이런 요소들(마지막 채팅으로 바로 업데이트)에서 상태 관리가 useEffect의 비동기와 만나면... 정말 까딱하면 엄청 헷갈리고 복잡해 질 수 있다는 걸 느꼈습니다... ㅜ..ㅜ
@@ -0,0 +1,15 @@ | |||
import React from 'react'; |
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.
피그마를 구경했는데 디자이너 님과의 QA가 정말 제대로 이루어진 것 같더라고요!!
디자인대로 UI를 잘 구현하는 것도 프엔 개발자의 덕목이라 생각하는데... 피그마에 나와있는 요구사항대로 잘 구현하셔서 너무 예쁘고 보기 좋은 UI인 것 같아용 ㅎㅎ 👍🏻👍🏻
@@ -0,0 +1,70 @@ | |||
import { useState } from 'react'; |
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.
@mimizae 지켜볼겁니다.
{/* Keyboard 추가 */} | ||
{isFocused && ( | ||
<div className="mt-2 transition-all duration-300 ease-in-out"> | ||
<KeyBoard onKeyPress={handleKeyPress} /> | ||
</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.
키보드가 너무 이뻐요. 키보드 올라올 때 뒤에 내용이 가려져서 키보드 올라올 때 메세지 최하단으로 강제 스크롤 시키면 좋을 것 같습니다
구현링크
피그마링크
💯 Key Questions
1. React Router의 동적 라우팅(Dynamic Routing)이란 무엇이며, 언제 사용하나요?
2. 네트워크 속도가 느린 환경에서 사용자 경험을 개선하기 위해 사용할 수 있는 UI/UX 디자인 전략과 기술적 최적화 방법은 무엇인가요?
3. React에서 useState와 useReducer를 활용한 지역 상태 관리와 Context API 및 전역 상태 관리 라이브러리의 차이점을 설명하세요.
😁 느낀점
4주차 과제를 의도치 않게 3주차에 많이 구현을 해놓았기 때문에 조금은 편하게 작업할 수 있었습니다. 다만 채팅방 전환기능을 생각못하고, 저번 과제에서 구현을 진행했었기 때문에 그런 흐름과 로직에 전환기능을 넣는 부분이 시간이 걸렸던 것 같습니다. 지금도 정말 완벽하게 코드상으로 바꾸는 건 아니지만 이 부분은 고민을 해봐야 할 것 같습니다. 그리고 가장 시간을 쓴 게 채팅방에서 description과 채팅 사이의 간격의 일관화를 위해 justify-between, margin, padding을 어떻게 해야 하나 고민했는데 Sparcer용도의 div태그를 넣어두고 간격이 유지되게끔하면서도 justify-between을 사용하여 위치를 고정시키는 방법을 배워갔습니다. 키보드가 올라올 때 채팅방도 같이 올라가도록 구현하고 싶었지만 더 고민해봐야 할 것 같습니다. 매우매우 부족함을 느꼈던 과제였던 것 같아 더 노력해서 많은 디테일을 공부하고 찾아봐야겠습니다. 코딩하는 모든 개발자분들 리스펙입니다.