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주차] 박지수 미션 제출합니다. #17

Open
wants to merge 27 commits into
base: master
Choose a base branch
from

Conversation

jsomnium
Copy link

@jsomnium jsomnium commented Nov 2, 2024

배포링크

dd

❤️ LINK ⭐
🎨 FIGMA 👗

느낀점

채팅방 구현 후에 채팅방 리스트와 친구 목록을 구현하니 코드를 수정할게 생각보다 많았습니다. 생각해보면 간단한건데, 코드를 수정해야한다고 생각하니 막막하더라구요.. 하하 아무튼 생각보다 손이 좀 가서 다음부터는 나중에 구현할 기능까지 생각해서 코드를 짜는게 나을 것 같네요.
로컬 스토리지 업로드 기능 구현하면서부터 어느 순간 새로고침 하면 max-width가 풀리는 버그가 생겼는데, 어디서부터 이 문제가 생긴지를 알 수가 없어서 해결하지 못했습니다.. ㅠ_ㅠ 그래도 계속 그런건 아니고 창 크기 줄였다 왔다갔다 하면서 크기 변경하니까 다시 돌아오긴 하더라구요. 다음부턴 계속 잘 살펴봐야겠습니다. 뭔가 계속 아쉬운 점이 남네요 😢
테일윈드도 처음 사용해서 그런지 완벽하게 익히기는 힘들었습니다. fontFamily에서 적용 안 되는 것도 있었고, 코드 가독성을 위해 미리 규격을 정해놓았으면 좋았을 것 같아서 다음번에는 그렇게 해보자 생각이 들었습니다. 근데 확실이 styledComponent 나 그냥 CSS 사용하는 것 보다는 편하더라구요. 확실히 눈에 보이기도 하고 따로 파일 구분할 필요도 없어서 파일 구조도 따로 안 짜도 돼서 간편한 것 같습니다! 다음 프로젝트도 테일윈드 사용해서 할 것 같습니다 😄

Key Question

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

  1. 동적 라우팅이란?
    동적 라우팅이란 웹 어플리케이션에서 클라이언트의 요청에 따라 동적으로 경로를 처리하는 라우팅 방식을 말한다. 이는 사용자의 입력, 상태 변화, 또는 다양한 조건에 따라 서버가 어떤 페이지나 리소스를 제공할지를 결정하는 것을 의미한다. 동적 라우팅은 정적 라우팅과 대조적으로, 라우팅 규칙이 사전에 하드코딩되어 있지 않고 실행 시에 결정된다.
  2. 동적 라우팅의 장점, 언제 사용하나?
    우선 정적 라우팅의 경우에는 라우터 컴포넌트에서 사용할 경로와 해당 경로로 접속 시 보여줄 컴포넌트를 미리 정의한다.
<Route path="/" element={<Main />}/>
<Route path="/login" element={<Login />}/>

하지만 이와 같은 방식으로 여러가지 상세페이지와 내용이 있게 된다면 이렇게 경로를 미리 설정하는 방식은 비효율적이다.
따라서 이런 문제점을 해결하기 위해 동적 라우팅 방식을 사용한다.
이를 위해 동적 라우팅은 url 형태를 미리 정의하지 않고, 특정 규칙을 정의한 후 규칙에 부합한 url이 있을 경우에 해당 엘리먼트를 보여준다.
3. 동적 라우팅 사용법
path prop에 : 기호를 사용해 경로/:문자열 형태로 설정하여 사용한다.

// 정적 라우팅
<Route path="/Route/" element={<Detail />} />

// 동적 라우팅
<Route path="/Route/: 문자열" element={<Detail />} />
<Route path="/Route/: id" element={<Detail />} />

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

  1. UI/UX 디자인 전략
    UI/UX 디자인 전략으로는 로딩 시각화를 들 수 있다. 네트워크 속도가 느려 페이지 로드가 오래 걸리는 경우에는 간단한 에니메이션을 활용해 로딩중임을 유저에게 인식시키면 사용자가 빈 화면을 보게 하는 것보다 기다림을 덜 느낄 수 있다. 이 경우에 페이지 로딩이 거의 다 된 것 처럼 보이게 하면 사용자의 기다림을 보다 오래 관리할 수 있다.
    또는 용량이 높은 요소들 (로딩이 오래 걸리는 요소들)을 미리 로드하지 않고, 사용자가 스크롤 할 시에 로드하도록 하는 방식도 있다. 하지만 이 방법은 처음 로딩되는 속도만 빨라질 뿐이지, 결과적으로 계속 로딩 속도가 느리다는 점은 변하지 않는다.

  2. 기술적 최적화 전략
    대표적으로 브라우저 캐싱이 있다. 데이터를 로컬에 저장하여 사이트 재방문시 빠르게 로드할 수 있다. 이는 최초 로딩 시에는 시간이 걸리지만, 동일 페이지 재방문 시에는 로딩 시간을 빠르게 단축할 수 있다. 캐시로 저장된 데이터는 로컬에서 바로 가져오기 때문에 데이터 로딩 속도나 네트워크에 의존하지 않으며, 속도가 느린 환경에서도 빠르게 로딩할 수 있다. 캐시는 많은 곳에서 쓰이고 있는 대표적인 방법이다.
    부가적으로, JavaScript 코드를 분할하여 필요한 JS 파일만 로드하도록 하는 것이 생각보다 로딩의 부담을 줄일 수 있다고 한다. 이와 마찬가지로 CSS도 스타일만 포함하고 사용하지 않는 파일 크기를 줄여 로딩 속도를 높일 수 있다.

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

  1. useState와 useReducer를 활용한 지역 상태 관리
    주로 컴포넌트 내부의 상태를 관리하는 데 사용된다. useState의 경우에는 사용이 간단하고 가벼운 상태 관리에 적합하고, useReducer의 경우에는 복잡한 상태 로직을 한 곳에서 관리할 수 있어 가독성이 좋고 디버깅이 용이하다는 장점이 잇다. 하지만 상태가 복잡해지면 관리하기 어려워 질 수 있다는 단점이 있다.
  2. Context API 및 전역 상태 관리 라이브러리
    여러 컴포넌트에서 공유해야 하는 전역 상태를 관리하기 위해 사용된다. Context API의 경우에는 중첩된 컴포넌트 간의 props drilling을 방지할 수 있고, 전역 상태 관리 라이브러리의 경우에는 중앙 집중화된 상태 관리로 복잡한 상태관리와 비동기 작업 처리를 지원한다. 하지만 상태가 자주 변경되면 모든 컴포넌트가 리렌더링되어 성능 저하 문제가 발생할 수 있다는 단점이 있다.

따라서 이 둘의 사용은 어플의 규모와 특징에 따라 달라진다.

@hiwon-lee
Copy link

이번 과제도 수고많으셨습니다. ㅎㅎ 디자인대로 이쁘게 잘 만들어주셨네요!
시험도 끝났으니 다시저희 다같이 열심히 해봐요..하하

Comment on lines +2 to +9
import NavBar from '../components/navBar';
import DateDivider from '../components/dateDivider';
import MyMessageBox from '../components/myMessageBox';
import OtherMessageBox from '../components/otherMessageBox';
import PlusButton from '../components/plusButton';
import ChatHeader from '../components/chatHeader';
import messagesData from '../data/chatData.json';
import ProfileButton from '../components/profileButton';

Choose a reason for hiding this comment

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

craco설치하면 절대경로 아래와 같이 심플하게 작성할 수 있답니다.
상대경로 쓰면 이후 파일위치 옮길때마다 수정하기가 상당히 귀찮은데, 이거쓰니까 좋더라구요

저희 과제란 밑에 참고 자료랑 아래의 자료 참고하면 반영할 수 있을 거예요~!
절대경로 설치

Suggested change
import NavBar from '../components/navBar';
import DateDivider from '../components/dateDivider';
import MyMessageBox from '../components/myMessageBox';
import OtherMessageBox from '../components/otherMessageBox';
import PlusButton from '../components/plusButton';
import ChatHeader from '../components/chatHeader';
import messagesData from '../data/chatData.json';
import ProfileButton from '../components/profileButton';
import NavBar from '@components/navBar'
import DateDivider from '@components/dateDivider';
import MyMessageBox from '@components/myMessageBox';
import OtherMessageBox from '@components/otherMessageBox';
import PlusButton from '@components/plusButton';
import ChatHeader from '@components/chatHeader';
import messagesData from '@data/chatData.json';
import ProfileButton from '@components/profileButton';

Copy link

Choose a reason for hiding this comment

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

ㅠㅠ 갑자기 끼어들어 죄송함니다만... 나도 이거 깔았었는데 갑자기 이유를알수없는에러가... ㅜㅜㅜㅜ 나서 삭제햇어요... 다시해보려고요... 너무좋은것가튼데 지금 경로가 '../../../../' 이 난리...

Choose a reason for hiding this comment

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

ㅠㅠ 갑자기 끼어들어 죄송함니다만... 나도 이거 깔았었는데 갑자기 이유를알수없는에러가... ㅜㅜㅜㅜ 나서 삭제햇어요... 다시해보려고요... 너무좋은것가튼데 지금 경로가 '../../../../' 이 난리...

왜지왜지..이거 처음 반영할때 조금 빡세
우선 craco설치하고,

 npm i @craco/craco

craco.config.jstsconfig.paths.json파일을 사진에 표시해둔 위치에 생성
image

이제 코드 작성해야하는데, 우선 craco.config.js파일에는 아래의 코드를 작성
이때, baseUrl잘 확인 (난 여기서 문제였어)

const { CracoAliasPlugin } = require('react-app-alias');

module.exports = {
  plugins: [
    {
      plugin: CracoAliasPlugin,
      options: {
        source: 'tsconfig',
        baseUrl: './src',
        tsConfigPath: './tsconfig.paths.json',
      },
    },
  ],
};

이후에 tsconfig.paths.json 파일 생성.(이것도 최상위 폴더 바로 아래다가) 여기서도 baseUrl제대로 확인해서 맞춰야돼
아래 코드는 내 플젝 기준인데, 각자 프로젝트 폴더 명으로 바꿔주면 됨

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@components/*": ["./components/*"],
      "@assets/*": ["./assets/*"],
      "@menuIcon/*": ["./assets/menu/*"],
      "@styles/*": ["./styles/*"],
      "@redux/*": ["./redux/*"],
      "@contexts/*": ["./contexts/*"],
      "@hooks/*": ["./hooks/*"],
      "@utils/*": ["./utils/*"],
      "@pages/*": ["./pages/*"],
      "@interface/*": ["./interface/*"],
      "@data/*": ["./data/*"]
    }
  }
}

image

이후에 무조건 서버 한 번 껐다가 재실행(재실행 안하면 반영안되더라구)
아 되면 좋겠는데..ㅠㅠ

Comment on lines +5 to +8

const ChatListRoom: React.FC = () => {
const userIds = [1]; // 목록에 랜더링할 사용자 ID

Choose a reason for hiding this comment

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

이 부분에서 사용자를 한 명밖에 안가져오게되네요
뭔가 이후 다수의 사람들과 채팅할때의 메시지 관리에서 문제가 있어서 일단 한명만 가져와서 진행하신것 같긴하지만, 이렇게 사용자 데이터에 있는 모든 사용자 아이디를 가져오면 데이터를 더 잘 활용할 수 있을 것 같습니다!

Suggested change
const ChatListRoom: React.FC = () => {
const userIds = [1]; // 목록에 랜더링할 사용자 ID
import userList from '../data/userData.json';
const ChatListRoom: React.FC = () => {
const userIds = userList.map((data) => data.id);

Comment on lines +10 to +17
// 사용자 프로필 상태
const [userProfile, setUserProfile] = useState<{ id: number, name: string } | null>(null);

// 생일인 친구 목록 상태
const [birthdayFriends, setBirthdayFriends] = useState<{ id: number, name: string }[]>([]);

// 친구 목록 상태
const [friends, setFriends] = useState<{ id: number, name: string }[]>([]);

Choose a reason for hiding this comment

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

{ id: number, name: string }이 부분이 계속 중복되니까 따로 타입이나 인터페이스로 빼주면 좋을 것 같습니다.

Suggested change
// 사용자 프로필 상태
const [userProfile, setUserProfile] = useState<{ id: number, name: string } | null>(null);
// 생일인 친구 목록 상태
const [birthdayFriends, setBirthdayFriends] = useState<{ id: number, name: string }[]>([]);
// 친구 목록 상태
const [friends, setFriends] = useState<{ id: number, name: string }[]>([]);
interface UserInterface{
id: number;
name: string;
}
const [userProfile, setUserProfile] = useState<UserInterface | null>(null);
const [birthdayFriends, setBirthdayFriends] = useState<UserInterface[]>([]);

Comment on lines +9 to +15
const handleTabClick = (icon: string) => {
setSelectedIcon(icon);
};

return (
<div className="w-full h-20 flex justify-center items-center space-x-7 pb-6">
<Link to="/" onClick={() => handleTabClick('/')}>

Choose a reason for hiding this comment

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

여기 라우팅을 중복으로 지정해주신것같아요
Link를 사용한다고 하시면 onClick은 안적어도 원하는 기능을 수행할 수 있을 것이고,
만약 onClick을 사용한다하시면 Link말고 <button>태그를 활용할 수 있을 것 같습니다.

개인적으로 후자의 방식은 따로 더 길게 handleTabClick과 같은 함수를 만들어 줘야하니까 Link를 활용하는 게 더 좋을 것 같아요!

Comment on lines +2 to +9
import NavBar from '../components/navBar';
import DateDivider from '../components/dateDivider';
import MyMessageBox from '../components/myMessageBox';
import OtherMessageBox from '../components/otherMessageBox';
import PlusButton from '../components/plusButton';
import ChatHeader from '../components/chatHeader';
import messagesData from '../data/chatData.json';
import ProfileButton from '../components/profileButton';

Choose a reason for hiding this comment

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

ㅠㅠ 갑자기 끼어들어 죄송함니다만... 나도 이거 깔았었는데 갑자기 이유를알수없는에러가... ㅜㅜㅜㅜ 나서 삭제햇어요... 다시해보려고요... 너무좋은것가튼데 지금 경로가 '../../../../' 이 난리...

왜지왜지..이거 처음 반영할때 조금 빡세
우선 craco설치하고,

 npm i @craco/craco

craco.config.jstsconfig.paths.json파일을 사진에 표시해둔 위치에 생성
image

이제 코드 작성해야하는데, 우선 craco.config.js파일에는 아래의 코드를 작성
이때, baseUrl잘 확인 (난 여기서 문제였어)

const { CracoAliasPlugin } = require('react-app-alias');

module.exports = {
  plugins: [
    {
      plugin: CracoAliasPlugin,
      options: {
        source: 'tsconfig',
        baseUrl: './src',
        tsConfigPath: './tsconfig.paths.json',
      },
    },
  ],
};

이후에 tsconfig.paths.json 파일 생성.(이것도 최상위 폴더 바로 아래다가) 여기서도 baseUrl제대로 확인해서 맞춰야돼
아래 코드는 내 플젝 기준인데, 각자 프로젝트 폴더 명으로 바꿔주면 됨

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@components/*": ["./components/*"],
      "@assets/*": ["./assets/*"],
      "@menuIcon/*": ["./assets/menu/*"],
      "@styles/*": ["./styles/*"],
      "@redux/*": ["./redux/*"],
      "@contexts/*": ["./contexts/*"],
      "@hooks/*": ["./hooks/*"],
      "@utils/*": ["./utils/*"],
      "@pages/*": ["./pages/*"],
      "@interface/*": ["./interface/*"],
      "@data/*": ["./data/*"]
    }
  }
}

image

이후에 무조건 서버 한 번 껐다가 재실행(재실행 안하면 반영안되더라구)
아 되면 좋겠는데..ㅠㅠ

Copy link

@ryu-won ryu-won left a comment

Choose a reason for hiding this comment

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

지수님! 과제하느라 고생많으셨습니다! 노력하신 부분이 많이 보여서 저도 많이 배워갑니다! 특히 코드가 깔끔해서 보기 좋았습니다ㅂ👍👍

placeholder="메시지를 입력하세요"
value={inputMessage}
onChange={handleInputChange} // onChange로 입력값 변경 추적
onKeyDown={sendMessage} // onKeyPress가 사용이 안된다네요? 왜지
Copy link

Choose a reason for hiding this comment

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

현재 맥북에서 메세지를 입력하면 2개의 메세지가 전송되는 오류가 있습니다! onkeydown보다는 onkeyup으로 수정하시면 될 거 같습니다!

Comment on lines +57 to +65
// 메시지 전송 시 스크롤 하단 이동
const scrollToBottom = () => {
messageScrollRef.current?.scrollIntoView({ behavior: "smooth" });
};

// 메시지 추가 시 (전송 시) 스크롤 하단 이동 함수 실행
useEffect(() => {
scrollToBottom();
}, [messages]);
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 scrollToBottom = () => {
messageScrollRef.current?.scrollIntoView({ behavior: "smooth" });
};
// 메시지 추가 시 (전송 시) 스크롤 하단 이동 함수 실행
useEffect(() => {
scrollToBottom();
}, [messages]);
useEffect(() => {
messageScrollRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);

으로 간결하게도 가능할 거 같습니다!

Comment on lines +100 to +121
let previousDate = "";

// 날짜를 yyyy.MM.dd 형식으로 변환하는 함수
const formatDate = (date: Date) => {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 2자리 월
const day = date.getDate().toString().padStart(2, '0'); // 2자리 일
return `${year}.${month}.${day}`;
};

return (
<div className="w-full h-full flex flex-col">
<NavBar />
<ChatHeader />

{/* 채팅 메세지 메인 Body */ }
<div className="flex-grow bg-aliceblue px-5 overflow-y-auto space-y-3" style={{ maxHeight: 'calc(100vh - 160px)' }}>
{/* 메시지 리스트 순회하면서 각각 렌더링 */}
{messages.map((message) => {
const currentDate = message.timestamp.split(' ')[0]; // 날짜 부분만 추출
const showDateDivider = currentDate !== previousDate; // 날짜가 다르면 DateDivider 렌더링
previousDate = currentDate; // 이전 날짜 업데이트
Copy link

Choose a reason for hiding this comment

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

{messages.map((message, index) => {
    const currentDate = message.timestamp.split(' ')[0];
    const showDateDivider = index === 0 || messages[index - 1].timestamp.split(' ')[0] !== currentDate;

    return (
        <div key={message.messageId}>
            {showDateDivider && <DateDivider date={currentDate} />}
            {/* 메시지 렌더링 */}
        </div>
    );
})}

이전 메시지의 날짜를 추적할 변수를 외부에서 선언하지 않고 메서드의 index를 활용해서 이전메세지와 비교해도 좋을 거 같습니다!

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.

4 participants