Skip to content
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

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,478 changes: 2,497 additions & 1,981 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "react-messenger-19th",
"name": "react-messenger-20th",
"version": "0.1.0",
"private": true,
"dependencies": {
Expand All @@ -8,13 +8,18 @@
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.91",
"@types/react": "^18.2.69",
"@types/react-dom": "^18.2.22",
"@types/react": "^18.3.8",
"@types/react-dom": "^18.3.0",
"lucide-react": "^0.454.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.26.2",
"react-scripts": "5.0.1",
"react-window": "^1.8.10",
Copy link

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와 비교해 알아보니 각각의 장단점을 훨씬 더 잘 이해할 수 있었어용 아직 모르신다면 찾아보시는 걸 추천 드리며...... ✨

"tailwind-scrollbar-hide": "^1.1.7",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
"web-vitals": "^2.1.4",
"zustand": "^5.0.0-rc.2"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉!! 전역 상태 관리에 zustatnd를 사용하셨군요... 뭔가 제가 다뤄보지 않은 기술을 사용하시는 걸 보면 항상 멋있는 것 같습니다... 👍🏻 그동안 Context API랑 대체 뭔 차이인지 아리송한 부분이 있었는데 전체적인 개념은 비슷할지언정 리렌더링 측면에서 훨씬 효율적이네요... 가볍기두 하고용... 반드시 공부해서 사용해 봐야겠습니다 여담이지만... 항상 이른 순서로 과제 제출하시는 게 참... 대단한 것 같습니다 🔥

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와 어떤 차이가 있는지 더 찾아보시고 공유 많이 해주세요 💪🏼

},
"scripts": {
"start": "react-scripts start",
Expand All @@ -39,5 +44,14 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0",
"@svgr/webpack": "^8.1.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"postcss-loader": "^8.1.1",
"tailwindcss": "^3.4.13"
}
}
7 changes: 7 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>ceos-messenger</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
38 changes: 0 additions & 38 deletions src/App.css

This file was deleted.

23 changes: 20 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import Profile from './app/profile/Profile';
import FollowList from './app/profile/follower/FollowList';
import ChatList from './app/chatting/ChatList';
import ChatRoom from './app/chatting/ChatRoom';
import Layout from './app/layout/Layout';

function App() {
return (
<div>
<h1>20기 프론트엔드 파이팅!!! 디자인과 사이좋게 지내요~~~</h1>
</div>
<Router>
<Routes>
{/* Layout을 기본적으로 적용할 라우트 그룹 */}
<Route path="/" element={<Layout />}>
<Route index element={<Navigate to="/profile" />} />
<Route path="/profile" element={<Profile />} />
<Route path="/profile/followList" element={<FollowList />} />
<Route path="/chatting/chatList" element={<ChatList />} />
<Route path="/chatting/chatRoom/:username" element={<ChatRoom />} />
</Route>
</Routes>
</Router>
);
}

Expand Down
26 changes: 26 additions & 0 deletions src/app/chatting/ChatList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import TNB from '../../components/Navigation/TNB';
import ListChat from '../../components/List/ListChat';
import useChatStore from '../../zustand/userStore';


export default function ChatList() {
const { users } = useChatStore();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저랑 같은 zustand를 사용하셨네요!!

return (
<div className='w-full h-full flex flex-col justify-center items-center'>
{/* ChatList TNB */}
<TNB name='chatlist'/>
<div className='w-full h-[40px] flex justify-between items-center px-4 py-2'>
<p className='text-title-2'>Messages</p>
<p className='text-body-2-b text-gray500'>Requests</p>
</div>

{/* ChatList User */}
<section className='w-full h-[636px] overflow-y-auto scollbar-hide'>
{users.map((user) => (
<ListChat key={user.user_id} user={user} />
))}
</section>
</div>
);
}
123 changes: 123 additions & 0 deletions src/app/chatting/ChatRoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import TNB from '../../components/Navigation/TNB';
import TextBubble from '../../components/ChatBar/TextBubble';
import useChatStore from '../../zustand/userStore';
import ChatBar from '../../components/ChatBar/ChatBar';
import { User } from '../../types/types';



const ChatRoom = () => {
const { username } = useParams(); // URL에서 유저명을 추출
const [userChange, setUserChange] = useState<boolean>(false);
const { users, getUserMessages } = useChatStore();


// 현재 시각
const formatTime = () => {
const date = new Date();
const options: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true // 12시간제로 표시 (AM/PM)
};
return date.toLocaleString('en-US', options).toUpperCase(); // 예: "SEP 2 AT 3:26 PM"
};

let currentUser = users.find(user => user.userName === username);



// 현재 유저의 메시지를 zustand로부터 가져옴
const messages = currentUser ? getUserMessages(currentUser.user_id) : [];
Copy link

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 스토어에서 처리하게 하는 거 어떨까용?!!! 🔥


// 유저 변경 시 currentUser 변경
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;
}
Comment on lines +38 to +48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요부분 set함수로 바꾸는게 더 직관적이지 않을까 싶습니다!

// 스크롤 자동 하강
const messagesEndRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const timeout = setTimeout(() => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, 500); // 100ms의 짧은 지연 시간 설정
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오!! 영준 님의 스크롤이 뭐랄까 되게 부드럽게 내려간다고 생각했는데 지연 시간을 짧게 설정해서 가능했던 거군요 ㅎㅎ UX가 훨씬 개선되는 것 같아요 👍🏻


return () => {
clearTimeout(timeout);
};

}, [messages, userChange]);
const handleChangeUser = () => {
setUserChange(!userChange);
}

return (
<div className="w-full h-full flex flex-col">
{/* ChatRoom TNB 렌더링 속도차이로 인한 조건문 header*/}
{currentUser && <TNB name="chatroom" user={currentUser} handleChangeUser={handleChangeUser} />}

{/* middle */}
<div className={`w-full h-full flex flex-col overflow-y-auto scrollbar-hide !important justify-between`}>
{/* 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>
Comment on lines +75 to +85
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 컴포넌트를 분리해도 될만한 부분은 컴포넌트 분리해서 불러오면 좀더 가독성이 좋을 것 같습니당

<button className='w-[112px] h-[32px] flex justify-center items-center px-4 py-[5px] rounded-lg bg-gray100 text-caption text-gray600 hover:bg-gray200 cursor-pointer'>View Profile</button>
</div>

{/* Spacer 요소 */}
<div style={{ minHeight: '200px' }} />

{/* ChatRoom Chat */}
<div className='w-full flex flex-col items-center justify-end space-y-5'>
{/* 채팅 시작 시각 */}
<span className='w-[108px] h-[16px] text-center text-body-3 text-gray500'>{formatTime()}</span>
{/* 채팅 내용 */}
<div className='w-full flex flex-col space-y-3 px-3'>
{messages.map((message, index) => (
<TextBubble
key={message.id}
text={message.text}
isMine={message.isMine}
user={currentUser}
index={index}
userChange={userChange}
/>
))}
</div>
{/* 스크롤 이동을 위한 빈 div */}
<div ref={messagesEndRef} />
Copy link

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로 설정해서 스크롤이 가장 아래로 이동하게 했습니다!

</div>
</div>
{/* Input Chatting Bar footer*/}
<div className='py-2'>
<ChatBar/>
</div>
</div>


);
};

export default ChatRoom;
24 changes: 24 additions & 0 deletions src/app/layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import GNB from '../../components/Navigation/GNB';
import ChatBar from '../../components/ChatBar/ChatBar';
// import NavBar from './NavBar'; // 하단의 네비게이션 바

const Layout = () => {
const location = useLocation(); // 현재 경로 정보를 가져옴
const isLocationCheck = location.pathname.startsWith("/chatting/chatRoom");

return (
<div className="flex flex-col w-[375px] h-[100vh] shadow-lg bg-white m-auto overflow-hidden">
<main className={`
w-full h-full
`}>
<Outlet /> {/* 자식 Route 컴포넌트가 여기에서 렌더링됩니다 */}
</main>
{/* <NavBar /> */}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 NavBar를 주석처리하신 이유가 따로 있으시ㄹ까요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 궁금합니다!! 🔎

{!isLocationCheck && <GNB />}
</div>
);
};

export default Layout;
43 changes: 43 additions & 0 deletions src/app/profile/Description.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import profileImage from '../../assets/Image/profile.jpg'
import { ReactComponent as LinkIcon } from '../../assets/svg/link.svg'
import { useNavigate } from "react-router-dom";

export default function Description() {
const navigate = useNavigate();

return (
<div className='w-full h-[243px] flex flex-col items-center'>
<header className='w-full h-[110px] flex items-center gap-[21px] pl-3 pr-[26px] py-4'>
<img src={profileImage} alt='profile' className='w-[88px] h-[88px] rounded-full cursor-pointer'/>
<div className='w-[228px] h-[40px] inline-flex gap-8'>
<span className='text-center cursor-pointer'>
<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'>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 button 태그를 쓰지 않고 span으로 처리한 이유가 있으신가요?

Copy link

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>
이 부분이 계속 반복이 되는 것 같아요!

저라면, 요 부분을 따로 빼서 중복을 줄일 것 같습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혜인 언니가 시멘틱 태그 짚어주셨는데 제 과제에서도 div를 습관적으로 써서 지금 div 밭이라 얼른 수정해야 할 것 같네용... 매번 까먹는 시멘틱 태그지만 기본적인 거일 수록 항상 중요한 것 같아용... 🥹

<p className='text-title-2'>1000</p>
<p className='text-body-2-m'>followers</p>
</span>
<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>
Comment on lines +22 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<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>

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 말이에요!! 저는 개인적으로 겹치는 컴포넌트 (그게 디자인이든 로직이든 관계없이)가 있으면 일단 최대한 공통으로 묶을 수 있는지부터 생각하는 편이라서 요렇게 제안드려 볼게요!

</div>
</header>
<section className='w-full h-[143px] flex flex-col gap-2 items-start px-4 py-3'>
<div>
<p className='h-[19px] text-body-2-b self-stretch'>민솔</p>
<p className='h-[19px] text-body-2-m self-stretch'>반갑습니다</p>
</div>
<div className='flex gap-2 items-center h-[18px] text-caption text-main'>
<LinkIcon className='fill-current text-main'/>
<a href="https://www.instagram.com/confiwns_/">https://www.instagram.com/confiwns_/</a>
</div>
</section>
<footer className='px-4'>
<button className='w-full h-[37px] inline-flex justify-center items-center gap-[10px] px-[135px] py-2 rounded-lg bg-gray100 text-gray600 text-center text-caption hover:bg-gray200'>Edit Profile</button>
</footer>
</div>
);
}
Loading