Skip to content

[4주차] 김홍엽 과제 제출합니다.#14

Open
Yeobi00 wants to merge 44 commits intoCEOS-Developers:masterfrom
Yeobi00:yeobi00
Open

[4주차] 김홍엽 과제 제출합니다.#14
Yeobi00 wants to merge 44 commits intoCEOS-Developers:masterfrom
Yeobi00:yeobi00

Conversation

@Yeobi00
Copy link
Copy Markdown

@Yeobi00 Yeobi00 commented Apr 23, 2026

배포 링크

🔗 https://ceos-week3-react-messenger-23rd-nine.vercel.app/

디자이너와 협업하며 전달받은 자료

피그마 링크

🔗 https://www.figma.com/design/es0RHym54Js6oyP8kgJDxb/CEOS-2%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B9%80%EC%A0%95%EC%9B%90?node-id=0-1&t=arMirpAfb4nTqRT9-1

커뮤니케이션 과정

Figma와 카카오톡을 통해 디자이너와 양방향 커뮤니케이션 및 QA 진행했습니다.

Review Questions

1. React Router의 동적 라우팅(Dynamic Routing)이란 무엇이며, 언제 사용하나요?

동적 라우팅

  • URL의 일부를 변수처럼 처리하여 하나의 라우트로 여러 페이지를 대응하는 방식
  • :로 시작하는 값을 dynamic segment라고 하며, 해당 값은 URL과 매칭되어 params로 전달됩니다.
  • 컴포넌트에서는 useParams를 통해 dynamic segment 값을 사용할 수 있습니다.
<Route path="/chat/:id" element={<ChatRoomPage />} />
import { useParams } from "react-router-dom";

const { id } = useParams<{ id: string }>();

사용 목적

  • 동일한 UI 구조에서 데이터만 달라지는 페이지 처리
  • 라우트 중복 정의 방지

 

2. 네트워크 속도가 느린 환경에서 사용자 경험을 개선하기 위해 사용할 수 있는 UI/UX 디자인 전략과 기술적 최적화 방법은 무엇인가요?

1) UI/UX 디자인 전략

  • 로딩 상태 시각화: 로딩 스피너, progress bar 등을 사용하여 사용자에게 로딩 상태를 명확히 보여줍니다.
  • Skeleton UI: 실제 콘텐츠 형태와 유사한 뼈대 UI를 먼저 렌더링하여 빈 화면보다 체감되는 속도를 개선합니다.
  • 점진적 렌더링: 모든 데이터를 기다리지 않고 텍스트 및 핵심 콘텐츠를 먼저 렌더링하고, 이미지나 추가 데이터는 이후에 렌더링합니다.
  • 낙관적 업데이트(Optimistic UI): 서버 응답 전에 UI를 먼저 업데이트하여 반응성을 높입니다. 단, 요청 실패 시 롤백 또는 에러 처리가 필요합니다. (ex: 좋아요 버튼)

2) 기술적 최적화

  • 코드 분할(Code Splitting): 필요한 코드만 먼저 로딩하여 초기 변동 크기를 줄입니다.
  • 이미지 최적화: WebP 등 효율적인 이미지 포맷을 사용하거나, 이미지 크기를 줄여 네트워크 비용을 낮춥니다.
  • 데이터 캐싱: React Query 등을 사용하여 이전 데이터를 메모리에 저장하고, 동일 요청 발생 시 재사용합니다.
  • Preload / Prefetch: 현재 페이지에서 곧 필요한 리소스는 preload로, 이후 이동할 가능성이 높은 페이지나 리소스는 prefetch로 미리 로드합니다.
  • 지연 로딩(Lazy Loading): 초기 렌더링에 중요하지 않은 이미지, 컴포넌트 등의 로딩을 필요 시점까지 미룹니다.

 

3. React에서 useState와 useReducer를 활용한 지역 상태 관리와 Context API 및 전역 상태 관리 라이브러리의 차이점을 설명하세요.

1) useStateuseReducer를 활용한 지역 상태 관리

  • 컴포넌트 내부 또는 특정 컴포넌트 하위 범위에서 상태를 관리할 때 사용합니다.

useState

  • 단순한 상태 관리에 적합합니다.
  • ex) input 값, 모달 열림 여부, 버튼 활성화 여부

useReducer

  • 상태 변경 로직이 복잡하거나 여러 조건에 따라 상태가 변경될 때 적합합니다.
  • 상태 업데이트 로직을 reducer 함수로 분리하여 관리할 수 있습니다.
  • ex) 여러 action에 따라 변경되는 form 상태, 복잡한 UI 상태

2) Context API

  • props를 여러 단계로 전달하지 않고, 하위 컴포넌트에서 필요한 값을 직접 사용할 수 있게 합니다.
  • props drilling을 줄일 수 있고, 여러 컴포넌트에서 공통으로 필요한 값을 전달할 때 유용합니다.
  • Context는 상태를 관리하기보다는, 값을 트리 하위로 전달하기 위한 메커니즘입니다.
  • useReducer와 함께 사용하면 여러 하위 컴포넌트에서 동일한 상태와 dispatch를 공유할 수 있습니다.

3) 전역 상태 관리 라이브러리

  • Redux, Zustand 등 전역 상태 관리 라이브러리는 여러 컴포넌트나 페이지에서 공유되는 상태를 별도의 store에서 관리하기 위해 사용합니다.
  • Context는 값을 전달하는 역할에 집중하는 반면, 전역 상태 관리 라이브러리는 상태 변경 흐름을 보다 구조적으로 관리할 수 있도록 다양한 기능을 제공합니다.
  • 라이브러리에 따라 성능 최적화, 상태 추적, DevTools, middleware, selector, persistence 등의 기능을 사용할 수 있습니다.

정리

  • 컴포넌트 내부의 단순한 상태 → useState
  • 상태 변경 로직이 복잡한 지역 상태 → useReducer
  • 여러 하위 컴포넌트에 값 전달 → Context API
  • 앱 전반에서 공유되고 변경 흐름 관리가 필요한 상태 → 전역 상태 관리 라이브러리

Yeobi00 added 30 commits March 26, 2026 19:27
@Yeobi00 Yeobi00 changed the title [4주차] 김홍엽 과제 제출합니다 [4주차] 김홍엽 과제 제출합니다. Apr 23, 2026
Copy link
Copy Markdown
Member

@chaeyoungwon chaeyoungwon left a comment

Choose a reason for hiding this comment

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

과제하시느라 수고 많으셨습니다!
전반적으로 서비스에 필요한 요소들을 잘 파악하고
피그마에서 디자이너분과 적극적으로 소통해주신 것 같아 인상 깊었습니당 👏

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

채팅 리스트 페이지에서는 실제 채팅 앱처럼,
채팅방 목록이 가장 최근에 전송된 메시지를 기준으로 최신순 정렬되도록 갱신하는 로직을 작성해보셔도 좋을 것 같습니다 !!

Comment on lines +54 to +56
className={`w-8 h-8 ${
isActive ? "text-primary" : "text-content-inactive"
}`}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

앞으로 조건부 스타일링을 자주 사용하시게 될 텐데, 이때 clsx 라이브러리나 cn 유틸 함수를 활용하시는 것을 추천드려봅니당

{tabs.map((tab) => {
const isActive = activeTab === tab.key;
return (
<button
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

버튼 요소에는 가급적 cursor-pointer 속성도 함께 추가해주시면 좋을 것 같습니다!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

특정 컴포넌트들에 memo를 적용해주셨는데, 어떤 기준으로 적용 여부를 판단하셨는지 궁금하네용 ~~

Comment on lines +28 to +35
useEffect(() => {
if (chatRoomId !== null) {
setActiveChatRoom(chatRoomId);
}
return () => {
setActiveChatRoom(null);
};
}, [chatRoomId, setActiveChatRoom]);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

현재 특정 채팅방에서 시점을 전환한 뒤 다른 채팅방으로 이동하면, 이전 채팅방에서 변경된 시점 상태가 전역으로 남아 있는 것 같은데요!

예를 들어 최혜정과의 채팅방에서 시점을 전환한 후 엄마 채팅방으로 이동하면, 원래 사용자 시점으로 초기화되지 않고 이전 시점이 유지되는 문제가 발생합니다.

Image
따라서 채팅방을 이탈할 때 currentUserId를 진입 당시의 사용자 값으로 복원하거나, 채팅방 내 시점 전환 상태를 별도 상태로 분리해 관리하는 방식도 고려해보시면 좋을 것 같습니다!

{ "id": 4, "name": "쩝", "participantIds": [0, 4] },
{
"id": 5,
"name": "이름이엄청나게길어질경우에는원채영바보",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👏

<div className="flex items-center justify-between px-5 py-3 h-12">
<span
className="text-h1-logo"
style={{ color: "var(--color-primary)" }}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

text-primary를 className에 바로 적용해주시면 될 것 같네용

Comment on lines +51 to +55
{chatRooms.length === 0 && (
<span className="mt-25 self-center text-body2-r text-content-hint">
현재 참여 중인 대화방이 없습니다.
</span>
)}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

피그마에 별도로 요청해주신 점 인상 깊네요!
실제 서비스에서도 다양한 상황이 발생할 수 있기 때문에, 앞으로도 이런 엣지 케이스까지 고려해 개발해주시면 좋을 것 같습니다 ~~

Comment on lines +8 to +16
import IconAddFriend from "@/assets/icons/icon_add_friend.svg?react";
import IconAddStory from "@/assets/icons/icon_add_story.svg?react";
import IconFacebook from "@/assets/icons/icon_facebook.svg?react";
import IconNotification from "@/assets/icons/icon_notification.svg?react";
import IconUserChat from "@/assets/icons/icon_user_chat.svg?react";
import IconUserMe from "@/assets/icons/icon_user_me.svg?react";
import IconUserStory from "@/assets/icons/icon_user_story.svg?react";
import IconUp from "@/assets/icons/icon_up.svg?react";
import IconDown from "@/assets/icons/icon_down.svg?react";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

아이콘 import문이 다소 길어지고 있어, assets/icons/index.ts에서 한 번에 모아 export한 뒤 가져오는 방식도 고려해보시면 좋을 것 같습니다!

Comment on lines +13 to +37
const tabs: {
key: TabKey;
label: string;
Icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
path: string;
}[] = [
{
key: "home",
label: "홈",
Icon: IconHome,
path: "/",
},
{
key: "chat",
label: "채팅",
Icon: IconChat,
path: "/chat",
},
{
key: "menu",
label: "메뉴",
Icon: IconMenu,
path: "",
},
];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

경로는 상수로 분리해서 관리해보셔도 좋을 것 같아요!
오타를 줄일 수 있고, 경로 변경 시에도 한 곳에서만 수정하면 된다는 장점이 있습니당

Copy link
Copy Markdown

@minseo0614 minseo0614 left a comment

Choose a reason for hiding this comment

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

전체적으로 디자인 토큰화, 컴포넌트 분리, 메시지 그룹핑 같은 핵심 영역에서 완성도가 높아서 코드 읽으면서 배운 점이 많았습니다!

Comment on lines +19 to +29
const filteredRooms = useMemo(() => {
const query = searchQuery.trim().toLowerCase();
if (!query) return chatRooms;

return chatRooms.filter((room) => {
if (room.name.toLowerCase().includes(query)) return true;
return getMessagesByChatRoomId(room.id).some((msg) =>
msg.content.toLowerCase().includes(query),
);
});
}, [chatRooms, searchQuery, getMessagesByChatRoomId]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

채팅방 이름뿐 아니라 메시지 내용까지 검색 대상에 포함된 점이 사용자 입장에서 실용적인 것 같아요!

Comment on lines +89 to +96
getUnreadCount: (chatRoomId, currentUserId) => {
const lastReadId = get().lastReadMessageId[chatRoomId] ?? 0;
return get().messages.filter(
(m) =>
m.chatRoomId === chatRoomId &&
m.senderId !== currentUserId &&
m.id > lastReadId
).length;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

unread를 별도 배열로 관리하지 않고 lastReadMessageId 하나만 저장한 뒤, 그 기준으로 카운트를 계산하는 방식이 좋았습니다. senderId !== currentUserId 조건으로 내가 보낸 메시지를 자연스럽게 제외한 점도 실제 메신저 동작과 잘 맞는 것 같습니다.

Comment on lines +37 to +41
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
e.preventDefault();
handleSubmit();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

MessageInput에서 Enter 전송을 처리할 때 !e.nativeEvent.isComposing 조건까지 둔 점이 좋은 것 같아요! 한글 조합 중 Enter로 메시지가 잘려 전송되는 버그를 막아주는 세심한 처리라고 생각했습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants