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주차] 이나현 미션 제출합니다. #16

Open
wants to merge 57 commits into
base: main
Choose a base branch
from

Conversation

CSE-pebble
Copy link

@CSE-pebble CSE-pebble commented May 3, 2024

3주차 미션: React-Messenger

🐻 배포 링크

https://discord-react-typescript.vercel.app/

👩‍💻 구현 기능

  • 하단바 아이콘을 누르면 해당하는 페이지로 라우팅
  • 채팅 목록 페이지에서 특정 채팅을 누르면 해당 채팅방으로 이동
  • 채팅방 메세지 보내기 기능
  • 채팅 상대 토글 기능
  • 유저 프로필 조회 및 유저 링크 바로가기 기능

🥳 후기

Redux, Routing 기능을 처음 공부해서 사용해봤습니다! 걱정을 많이 했는데 생각보다 Redux가 어렵지 않았고 코드 구현을 하면서 재미도 많이 느꼈습니다. Routing도 페이지가 많지 않아서 그런 건지 쉽게 구현했습니다.

오히려 타입스크립트와 localStorage 관련 부분에서 더 어려움을 겪었던 것 같습니다.

그리고 프로젝트를 하면서 점점 느끼는 것은 무작정 코드를 짜기 시작하는 것보다 전체적인 구조와 세부 컴포넌트 틀, 전역으로 관리할 상태 등을 미리 정리하고 코드를 짜는 게 중요하다는 생각이 들었습니다. 그 과정이 처음에 할 때 오래 걸릴지 몰라도 개발에 들어가면 훨씬 수월하게 개발할 수 있는 것 같습니다.

💡 새롭게 배운 점

  • Redux를 이용한 전역 상태관리
  • Routing 기능

🔥 어려웠던 부분 / 의문점

  • localStorage: localStorage에 저장될 때 key가 문자열 타입이라는 것을 잊고 에러 해결하는데 한참을 헤맸습니다🥲
  • 타입스크립트: 어디부터 어디까지 타입을 작성해주어야 하는지 아직 감이 잘 오지 않습니다. 그리고 어떻게 작성해야 가독성이 좋을지도 고민이 많이 되는 것 같아요. 좀 더 공부가 필요할 것 같습니다!

✨ 더 구현해보고 싶은 기능

  • 시간 관계 상 친구 목록 페이지는 별다른 기능 없이 이미지로만 넣어뒀는데 수정하고 싶습니다. 여유가 되면 시도해보겠습니다.
  • 채팅 목록 최신순 정렬 기능
  • 검색 기능(현재는 그냥 이미지)
  • 채팅방별 id로 구분되는 라우팅

❓ Key Questions

1. Routing이란?

  • 웹 애플리케이션에서 라우팅이라는 개념은 사용자가 요청한 URL에 따라 알맞는 페이지를 보여주는 것
  • ex) 블로그를 만든다면?
    • 글쓰기 페이지: 새로운 포스트를 작성하는 페이지
    • 포스트 목록 페이지: 블로그에 작성된 여러 포스트들의 목록을 보여주는 페이지
    • 포스트 읽기 페이지: 하나의 포스트를 보여주는 페이지
  • 리액트에서 라우팅을 하기 위한 대표적인 두 가지 방법
    • 리액트 라우터(React Router): 이 라이브러리는 리액트의 라우팅 관련 라이브러리들 중에서 가장 오래됐고, 가장 많이 사용되고 있다. 이 라이브러리는 컴포넌트 기반으로 라우팅 시스템을 설정할 수 있다.
    • Next.js: Next.js 는 리액트 프로젝트의 프레임워크이다. 이 프레임워크는 우리가 사용했던 Create React App처럼 리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어 시스템 지원, 서버 사이드 렌더링 등 다양한 기능들을 제공한다. → 이 프레임워크의 라우팅 시스템은 파일 경로 기반으로 작동하여 파일경로만 설정해주면 라우팅이 자동으로 적용되는 형태이다

2. SPA란?

  • 싱글페이지 어플리케이션이란 index.html이 하나인, 한 개의 페이지로 이루어진 어플리케이션이라는 의미
    ↔ 멀티 페이지 애플리케이션에서는 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고, 페이지를 로딩할 때마다 서버에서 CSS, JS, 이미지 파일 등의 리소스를 전달받아 브라우저 화면에 보여 주었다 !
  • 사용자 인터랙션이 별로 없는 정적인 페이지들은 기존의 방식(MPA)이 적합하지만, 사용자 인터랙션이 많고 다양한 정보를 제공하는 모던 웹 애플리케이션은 이 방식이 적합하지 않음 → SPA를 통해 html을 한 번만 받아와서 웹 어플리케이션을 실행시킨 후에, 그 이후에는 필요한 데이터만 화면에 업데이트!

3. 상태관리란?

  • 데이터를 관리하는 방법. 여러 component간에 데이터 전달과 이벤트 통신을 한 곳에서 관리하는 것.
  • 상태 관리가 필요한 이유
    1. 데이터가 바뀌어도 페이지가 렌더링 되지 않게 하기 위해
    2. 상태(state)들이 복잡하게 얽혀있다면, 상호간에 의존성이 많아지게 되어서 UI가 어떻게 변하는지 알기 어렵기 때문에 효율적인 관리가 필요하다.
  • 상태 관리 라이브러리 종류 : redux,MobX,Overmind.js,recoil

- localStorage의 key는 string이고, 이것을 id라는 props에 숫자처럼 전달해줘서 생긴 문제였다.. id는 "1"처럼 문자로 전달되고 있었는데 나는 숫자로 전달되는지 알고 있었다.. 으악 콘솔 찍어봐도 모르겠더라
Copy link

@Rose-my Rose-my left a comment

Choose a reason for hiding this comment

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

redux는 안써보고 recoil만 써봤는데... 너무 대단해요 > <

새로 알게된 코드도 많았고 전역상태라이브러리랑 라우팅 처음이라고 하셨는데 짱짱 수고하셨어요 👍😊

Copy link

Choose a reason for hiding this comment

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

jsx와 tsx는 컴포넌트 관련 파일에만 사용해서 그래서 GlobalStyle처럼 리액트 컴포넌트를 사용하지 않는 스타일 파일은 보통 ts 확장자를 많이 쓴다고 해요 > <

Comment on lines +53 to +54
<Wrapper>
{buttonData.map(({ id, text, selectedIcon, unselectedIcon }) => (
Copy link

Choose a reason for hiding this comment

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

저의 경우에는 여러 개 props인 경우 가독성을 위해 아래와 같이 구조분해 할당을 하는데요, 기능은 같으니까 참고만하시면 될 것 같아요 ㅎㅎ

.map((btn) => {
     const { id, text, selectedIcon, unselectedIcon } = btn;

Comment on lines +86 to +95
const ButtonWrapper = styled.div<{ isactive: boolean }>`
display: flex;
flex-direction: column;
gap: 0.25rem;
align-items: center;
cursor: pointer;

// 선택된 상태일 때 스타일 적용
color: ${(props) => (props.isactive ? colors.black : colors.gray300)};
`;
Copy link

Choose a reason for hiding this comment

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

styled component에 props 전달까지 최곤데요 ? ㅎㅎ

그런데 한가지 주의해야할 점이 있는데요 바로 표준 속성 워닝입니당 ㅎㅎ

표준 속성 워닝이란?
React와 styled-components에서는 표준 HTML 속성이 아닌 사용자 정의 속성이 DOM 엘리먼트에 전달되는 것을 기본적으로 허용하지 않아서 발생하는 에러

따라서 styled component 5.1버전 이상부터는 $를 prefix로 써서 warning을 없애고 DOM에 불필요한 속성이 전달되는 것을 막아주었어요 !!

혹시나 구체적인 내용 더 확실히 알고 싶을 수 있으니 관련 아티클 첨부할게요 > <

Copy link

Choose a reason for hiding this comment

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

전달할 때 isactive$isactive으로 !!

icon?: string | null;
}

const PageHeader = ({ title, search, icon }: PageHeaderProps) => {
Copy link

Choose a reason for hiding this comment

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

Suggested change
const PageHeader = ({ title, search, icon }: PageHeaderProps) => {
const PageHeader = (PageHeaderProps) => {
const {title, search, icon} = PageHeaderProps

Copy link

Choose a reason for hiding this comment

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

요기도 이렇게 바꿔보면 개인적으로 가독성이 더 좋더라고요 ㅎㅎ

{
"text": "짱구야 노올자~",
"sender": 0,
"timestamp": "5월 3일 (금) 9:21 AM"
Copy link

Choose a reason for hiding this comment

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

저번 코드리뷰때 저도 알게된 사실인데 보통 서버에서 받은 응답은

"createdAt": "2023-12-31T21:28:50.589Z" 같이 ISO 8601 날짜 및 시간 포맷을 따르는 문자열이 반환된다고 하네요 > <

리팩토링하면서 참고해도 좋을 것 같아요 👍

Comment on lines 36 to 37
<Profile
src={`img/userProfile/${userData.users[idNum].profileImg}`}
Copy link

Choose a reason for hiding this comment

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

저는 이미지 배열을 굳이굳이 만들어서 map했는데 url로 식별하는 방법 너무 좋은데요? > <


ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<Provider store={store}>
Copy link

Choose a reason for hiding this comment

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

redux는 Provider...! 신기하네요 > <

Comment on lines +241 to +243
&::placeholder {
${colors.gray300}
}
Copy link

Choose a reason for hiding this comment

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

placeholder까지!! 최고입니당

Comment on lines +4 to +10
interface TemplateProps {
children: ReactNode;
}

const Template = ({ children }: TemplateProps) => {
return <Wrapper>{children}</Wrapper>;
};
Copy link

Choose a reason for hiding this comment

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

중첩라우팅보다 나현님처럼 children으로 넘겨주는게 props drilling 해결 측면에서 더 나을 것 같기도 하네용
덕분에 children 어떻게 쓰는지 감이 잡혔어요 > <

Comment on lines +190 to +202
const MsgText = styled.span<{
bgcolor: string;
txtcolor: string;
maxwidth: number;
}>`
max-width: ${(props) => props.maxwidth}rem;
${typography.body2};
background-color: ${(props) => props.bgcolor};
border-radius: 1.25rem;
padding: 0.75rem;
color: ${(props) => props.txtcolor};
text-align: start;
`;
Copy link

Choose a reason for hiding this comment

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

처음 보는 형태에요 !! 완전 신기하네요 ㅎㅎ

Comment on lines +6 to +11
import friend_selected from "../../assets/footer/friend_selected.svg";
import friend_unSelected from "../../assets/footer/friend_unSelected.svg";
import chatting_unSelected from "../../assets/footer/chatting_unSelected.svg";
import chatting_selected from "../../assets/footer/chatting_selected.svg";
import you_selected from "../../assets/footer/you_selected.svg";
import you_unSelected from "../../assets/footer/you_unSelected.svg";
Copy link

Choose a reason for hiding this comment

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

import 문이 많으면 가독성이 안좋을 수 있으니 index.ts하나 만들어서 요것들을 싹 넣어놓고

import {friend_selected, friend_uselected, ...} from "../assets"

여기서는 이렇게 한줄로 한번에 처리하는 방법도 있어요 ㅎㅎ

Comment on lines 11 to 12
<Route path="/" element={<FriendsList />} />
<Route path="/*" element={<FriendsList />} />
Copy link

Choose a reason for hiding this comment

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

path를 /*로 설정한 이유가 궁금하네용 !!

Copy link
Author

Choose a reason for hiding this comment

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

아앗 제가 배포 오류 수정하고 있을 때 리뷰 남겨주신 것 같아요ㅎㅎ 리팩토링하면서 이 부분 쓸모없는 것 같아서 삭제했습니다!!

Copy link

@songess songess left a comment

Choose a reason for hiding this comment

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

이번주 과제 고생하셨습니다!
redux를 사용한 상태관리와 localStorage 저장까지 구현해주셔서 멋진 디스코드가 완성된 거 같아요! 리팩토링을 하게된다면 반응형을 고려해서 만들어보면 좋을 거 같아요!

"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"typescript": "^5.2.2",
"vite": "^5.2.0"
Copy link

Choose a reason for hiding this comment

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

vite를 설치하신 이유가 있는지 궁금합니다!

Copy link

Choose a reason for hiding this comment

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

일반적으로 index.html파일은 public에 넣어서 사용하는데, src에서 바로 사용하신 이유가 있을까요?

Copy link

Choose a reason for hiding this comment

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

App.js에서 렌더링되는게 하나라면, 해당컴포넌트를 바로 App.js에서 사용해도 괜찮을 것 같아요!

Comment on lines +10 to +15
<Routes>
<Route path="/" element={<FriendsList />} />
<Route path="/chats" element={<ChattingList />} />
<Route path="/room" element={<ChattingRoom />} />
<Route path="/profile" element={<MyProfile />} />
</Routes>
Copy link

Choose a reason for hiding this comment

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

사용자가 올바르지 않은 URL을 타고 들어올 수도 있는데 이럴때는 대비한 에러페이지를 만들어줬으면 좋았을 것 같아요!

Comment on lines +2 to +5
import ChattingList from "../pages/ChattingList/ChattingList";
import ChattingRoom from "../pages/ChattingRoom/ChattingRoom";
import FriendsList from "../pages/FriendsList/FriendsList";
import MyProfile from "../pages/MyProfile/MyProfile";
Copy link

Choose a reason for hiding this comment

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

상대경로를 사용해도 지장없지만 baseUrl과 path를 활용해서 절대경로를 사용해도 좋았을 거 같아요! 현재 저희는 cra를 사용하고 있기에 커스터마이징을 위해 craco,react-app-alias를 사용해야 하긴 하지만 한번 쯤 해보는 것도 좋은 거 같아요! 참고자료 첨부할게요!


const Wrapper = styled.div`
width: 375px;
height: 812px;
Copy link

Choose a reason for hiding this comment

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

이번 과제는 375x812크기에 맞는 구현이라 요구사항은 아니였지만, 맥북같은 경우 기본 뷰가 812px보다 작기 때문에 화면이 전부 안보여요! 리팩토링 할때height: 100dvh혹은 height: 100%를 사용해 반응형을 고려하면 더 좋을 거 같아요.

<Wrapper>
<TopWrapper>
<Title>{title}</Title>
{icon && <img src={editprofile} alt="프로필 편집" />}
Copy link

Choose a reason for hiding this comment

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

optional(?)을 사용해 확장성있는 컴포넌트가 되어서 좋아요!

import you_selected from "../../assets/footer/you_selected.svg";
import you_unSelected from "../../assets/footer/you_unSelected.svg";

const buttonData = [
Copy link

Choose a reason for hiding this comment

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

공통되는 버튼들을 배열형식으로 구현해서 가독성이 훨씬 좋아요!

Comment on lines +66 to +72
if (!isEmpty) {
const newChat = {
text: value,
sender: currOpponent === 0 ? opponent : 0,
timestamp: getDate(),
};
dispatch(chatsActions.addChat({ newChat, opponent }));
Copy link

Choose a reason for hiding this comment

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

동일한 채팅방에서 같은 시간에 동일한 텍스트를 입력하면 완전히 동일한 객체가 생성되어 배열에 push되는거 같아요. 이렇게 하면 나중에 변경하거나 삭제하려고 할 때 오류가 발생할 수 있으니, 고유키를 넣어주면 더 좋을 거 같아요!

return (
<ChattingItemWrapper key={index}>
<TimeStamp>{chat.timestamp}</TimeStamp>
<MsgBox align={isMine ? "end" : "start"}>
Copy link

Choose a reason for hiding this comment

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

동적스타일링을 통해 나의 채팅인지 상대의 채팅인지 구분할 수 있어서 좋아요! 하지만 이대로 콘솔창을 켜보면 아마 에러가 뜰 거에요. 스타일드컴포넌트를 위한 props전달은 DOM으로 전달되는걸 방지하기 위해 $을 사용한다고 해요!

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