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주차] 최지원 미션 제출합니다. #15

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

Conversation

jiwonnchoi
Copy link

@jiwonnchoi jiwonnchoi commented Nov 2, 2024

💌 구현물

default.mp4

(화질이 살짝 구린 점.. 양해부탁드립니다🥺)

🎨 피그마

💁‍♀️ 구현기능

(기본)

  • 친구 목록 기능
  • 친구 목록, 채팅방 목록, 채팅방 페이지 구성 (이전 채팅 유/무 구분)
  • 로컬스토리지 저장
  • 메시지에 유저 정보 표시

(추가)

  • 이미지 전송 + 새로고침 시에도 유지
  • 채팅방 핀 설정/해제 (카메라 아이콘 누르면 핀 아이콘 뜸)
  • 마지막 업데이트 시간 표기 (분/시간/날짜 단위)
  • 프로필페이지 슬라이더
  • 반응한 감정 유저 전환 시에도 유지 (로컬스토리지 저장)
  • 채팅방 목록 유저명으로 검색

💦 어려웠던 점 및 느낀점

타입스크립트를 써본지 2주차인데, 아직 적응이 되지 않아 타입관련 에러가 정말 자주 났습니다.. props를 전달해주면서 interface로 인자와 타입을 추가해주는 것에는 익숙해졌으나 undefined가 들어갈 수 있는 곳/없는 곳 등에 유의하며 정의해주고, 값을 넣어주고 (특히 id 유의..), 혹은 undefined가 아닐 때라는 조건부를 처리해주는 과정이 많은 에러를 마주해야 해서 까다로웠습니다.
이미지 전송에서도 많은 난관이 있었습니다. (살짝 구구절절.. 길어져서 토글 열면 이어집니다ヾ(°∇°*)

더보기
이미지 파일의 타입인 'File'을 처음엔 chatInterface의 message 프로퍼티에 or로 해서 타입을 추가해주면 될 것이라 생각했지만, 기존의 텍스트 메시지 처리대로 string만 들어가야하는 곳이 많아 어려웠습니다. file이라는 프로퍼티를 새로 추가해줄까도 했지만, 모든 메시지가 이미지 파일이 아니기도 하다는 점에서 undefined가 다수 들어가면 처리가 어렵겠다고 생각하여 결국 메시지를 처음 입력받는 InputBox에서 message에 File 타입을 추가해주고, 메시지 전송 함수인 sendChat으로 전달하여 그 안에서 텍스트 메시지와 (이미지)파일 메시지를 구분해주었습니다. 이미지를 전송하는 것에는 성공했으나, 로컬스토리지에 저장하는 과정에서 또 한번 막혔는데요.. 로컬스토리지에 이미지는 저장할 수 없다는 점을 그 이후에 깨달았기 때문입니다.. 이미지 전송까지 성공했을 때 사용했던 URL.createObjectURL() 함수는 1회성이라 로컬스토리지에 저장이 불가했고, 역시나 애플리케이션에도 빈 객체로 들어있었습니다. 방법을 찾아본 끝에 해당 파일 객체를 Base64로 변환하면 url 형태로 저장할 수 있었고, 이미지 데이터의 시작하는 경로를 통해 찾아서 데이터 띄우기와 ls 저장까지 구현할 수 있었습니다. 🥲
핀 기능은 배열로 chat id들을 추가해주는 것은 간단했지만 고정하는 순서에 따라 상단에 최신으로 업데이트 되도록 정렬하는 계산을 처리해주어야 했습니다. 참고로 핀은 피그마 디자인에 없었는데 프론트 추가 구현기능으로 있어서 디자이너분께 연락드리기엔.. 과제 마감까지 얼마 남지 않아 제가 임의로 넣어보았습니다.. (다음부터는 꼭 미리미리 해서 디자이너분께 여쭤보고 상의를 할 수 있도록 하겠습니다..🥲🥲)
생각보다 기본 기능은 지난 주차에서 조금만 추가하면 되어서, 추가 기능에서 여러 도전을 해보고자 하였습니다. 타입스크립트에 대한 이해가 더 많이 필요하겠다는 생각이 들었고, 추가적인 기능들을 구현함에 있어서 서버 없이 프론트 단에서 다 처리해주는 것이 번거로울 때 백엔드의 소중함을 느끼기도 하였습니다. 또 완료한 이후에 보니 MyChat.tsx와 ReceivedChat.tsx를 한 파일로 처리할 수도 있지 않았을까.. 하는 등의 몇몇 효율적이지 못한 코드에 대한 아쉬움도 남았습니다. 상태관리 라이브러리로는 이전에 써본 리코일이 편하다는 단순한 이유로 3주차에 쓰기 시작했는데, 이번 로컬스토리지 저장을 해야 하면서 알게 된 사실이 recoil-persist를 통해 {persistAtom}을 정의하고 해당 state에 `effects_UNSTABLE`로 atom을 지정해주면 쉽게 로컬스토리지에 저장하고 가져와 쓸 수 있다는 점이었습니다 ! 하지만 리코일은 더이상 업데이트가 되지 않는 라이브러리라고 알고 있어서 다음엔 zustand를 써보겠다고 생각했었는데, zustand에도 이런 간편한 점이 있을지 궁금했습니다.

🔑 Key Questions

( 1 ) React Router의 동적 라우팅(Dynamic Routing)이란 무엇이며, 언제 사용하나요?
동적 라우팅은 url 전체 형태를 미리 정의하지 않고 특정 규칙을 정의한 후, 규칙에 부합한 url이 있을 때 해당 element가 보여지는 방식을 말한다. 이와 반대되는 개념인 정적 라우팅은 라우터 컴포넌트에서 사용할 경로와 해당 경로로 접속할 시 보여줄 컴포넌트를 미리 정의하는 방식이다. 주로 규모가 크고 복잡한 애플리케이션에서 경로를 미리 설정하기 어려울 경우 (ex. 목록이라면 리스트 페이지와 각 상세페이지를 모두 각각 라우팅하는 것은 비효율적) 에 동적라우팅을 사용한다.

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

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

추가로, useParams hook을 사용하면 path parameter을 가져와 사용할 수 있다. state처럼 params의 값이 변함에 따라 컴포넌트를 리렌더링할 수 있다.

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

<UI/UX 디자인 전략>

  • 반응형 디자인 적용: 반응형 디자인은 하나의 웹사이트가 다양한 기기에서 최적화된 형태로 표시되도록 하여, 사용자가 어떤 디바이스를 사용하더라도 편리하게 정보를 탐색할 수 있도록 한다.
  • 사용자 조사 및 피드백: 사용자 조사는 설문조사, 인터뷰, 사용자 테스트 등 여러 가지 접근 방식을 활용하여 사용자의 의견을 수집한다. 피드백은 사용자 조사에서 수집된 정보를 바탕으로 이루어지며, 이는 제품의 기능이나 디자인에 대한 사용자들의 생각을 직접적으로 반영한다.

<기술적 최적화 방법>

  • 이미지 파일의 최적화: 파일 크기를 줄이고 해상도를 조정하여 로딩 속도를 개선하는 과정을 포함한다. 예를 들어, 사진에는 JPEG 형식이 적합하지만, 투명한 배경이 필요한 경우 PNG 형식이 더 알맞다. 또한, 웹에서 사용되는 이미지의 크기를 적절히 조정하여 불필요한 데이터 전송을 최소화할 수 있다.
  • 캐싱과 콘텐츠 전송 네트워크(CDN)의 활용: 캐싱은 웹사이트의 콘텐츠를 사용자 브라우저에 저장하여 이후 방문 시 빠르게 로딩할 수 있게 해준다. CDN은 지리적으로 분산된 서버를 통해 콘텐츠를 제공하여 사용자와의 물리적 거리를 줄임으로써 로딩 속도를 향상시킨다

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

지역 상태: 특정 컴포넌트 안에서만 관리되는 상태로, 다른 컴포넌트들과 데이터를 공유하지 않는다.

  • useState: 하나의 상태만 관리, 화살표 함수로 이전 상태를 받아서 현재 상태를 업데이트 해야 최신 상태를 보장함
  • useReducer: 여러 상태를 함께 관리, 최신 상태를 보장하여 간단하게 코드를 쓸 수 있음, reducer 함수, 초기 상태값, 초기 함수 인수를 받고 해당 함수를 호출하면 배열이 반환됨

전역 상태: 프로젝트 전체에 영향을 끼치는 상태

  • Context API: React 컴포넌트 트리 안에서 전역 상태를 공유할 수 있도록 만들어진 방법, Context - 전역 상태를 저장하는 곳 /Provider - Context에 상태를 제공해서 다른 컴포넌트가 상태에 접근할 수 있도록 도와주는 역할 /Consumer - 제공받은 전역 상태를 받아서 사용하는 역할로 구성되어 있음.

@jiwonnchoi jiwonnchoi changed the title [4주차] 최지원 과제 제출합니다. [4주차] 최지원 미션 제출합니다. Nov 2, 2024
<textarea
value={inputText}
onChange={handleChange}
onKeyDown={handleEnterSubmit}
Copy link

@westofsky westofsky Nov 4, 2024

Choose a reason for hiding this comment

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

image

사진처럼 onKeyDown 썼을 때 한글 마지막이 두번 입력 되는데 한번 읽어보세요

Copy link
Member

Choose a reason for hiding this comment

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

퉁퉁이..

Copy link

@hae2ni hae2ni left a comment

Choose a reason for hiding this comment

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

진짜 하나하나 고민하고 추가하고 고생한게 느껴지는 과제였어요!! 많이 배우고 가요!! 하나하나 더 꼼꼼히 보고 싶었는데 시간상 이슈로ㅠㅠㅠ

import { chattingState, userState } from "../../recoil/atom";
import { useEffect, useState } from "react";

import userData from "../../data/UserData.json";
Copy link

Choose a reason for hiding this comment

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

파일 경로가 점점 길어질 경우에는 미리 절대경로를 설정해서 사용하는 걸 추천드려요!

setSearchInput(e.target.value);
};

// 상단 고정 기능
Copy link

Choose a reason for hiding this comment

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

요거는 머를 고정한 건지 알 수 있을까용?

Copy link
Author

Choose a reason for hiding this comment

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

채팅목록에서 특정 채팅방 상단 고정입니다!

}
};

const filteredFollowers = chattings
Copy link

Choose a reason for hiding this comment

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

요 부분이 채팅을 정렬하는 부분같은데, 매번 렌더링되는게 조금 무거울 수도 있으니까, useMemo등으로 묶어주시는 건 어떨까용?

<div className="flex w-full flex-row gap-2 py-2">
<img
className="h-14 w-14 flex-shrink-0 rounded-full"
src={require("../../assets/images/" + follower.profileImg + ".svg")}
Copy link

Choose a reason for hiding this comment

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

요거 경로를 미리 앞에서 선언하고 가져오는 건 어떨까요!!

Copy link
Author

Choose a reason for hiding this comment

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

require을 쓰지 않으면 에러가 나서 감싸느라 요런 식으로 썼는데 앞에서 선언하는 방식도 시도해보겠습니다!

className="flex flex-grow cursor-pointer flex-col"
>
<div className="flex flex-row items-center">
<span className="text-[0.8125rem] font-semibold tracking-tighter">
Copy link

Choose a reason for hiding this comment

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

이런 부분은 시멘틱 태그를 사용해서 p 태그로 해주시면 더 좋을 것 같다는 그냥 개인적인 생각입니다!!!

)}

{selectedEmotion && (
<>
Copy link

Choose a reason for hiding this comment

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

요 빈태그는 필요 없을 거 같아용!

{isLastChat ? (
<img
className="h-7 w-7 rounded-full"
src={require("../../assets/images/" + profileImg + ".svg")} //props로 넘겨온 상대경로는 깨짐
Copy link

Choose a reason for hiding this comment

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

Suggested change
src={require("../../assets/images/" + profileImg + ".svg")} //props로 넘겨온 상대경로는 깨짐
src={require(`../../assets/images/${profileImg}.svg`)}

Comment on lines +71 to +79
{isImageMessage ? (
// 이미지 메시지인 경우
<img
src={message}
alt="sent image"
className={`ml-2 mr-auto w-[10.0625rem] rounded-[1.25rem] ${isLastChat ? "mb-[0.3125rem]" : ""}`}
/>
) : (
// 텍스트 메시지인 경우
Copy link

Choose a reason for hiding this comment

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

Suggested change
{isImageMessage ? (
// 이미지 메시지인 경우
<img
src={message}
alt="sent image"
className={`ml-2 mr-auto w-[10.0625rem] rounded-[1.25rem] ${isLastChat ? "mb-[0.3125rem]" : ""}`}
/>
) : (
// 텍스트 메시지인 경우
//상단에
const renderMessageContent = () => {
if (isImageMessage) {
return (
<img
src={message}
alt="sent image"
className={`ml-2 mr-auto w-[10.0625rem] rounded-[1.25rem] ${isLastChat ? "mb-[0.3125rem]" : ""}`}
/>
);
}
return (
<div className="ml-2 mr-auto inline-flex max-w-[13.375rem] items-center break-all rounded-[1.25rem] bg-Chat_BG px-2.5 py-2 text-[0.9375rem] tracking-tighter text-white">
{message}
</div>
);
};
//사용
{renderMessageContent ()}

Copy link
Author

Choose a reason for hiding this comment

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

조건부로 코드 길어지는 부분이 많았는데 다음엔 함수화 해서 써보겠습니다🥹💫


return (
<>
<div className="mt-[1.31rem] flex flex-col px-[0.88rem]">
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 +11 to +12
<div className="flex w-[7.75rem] items-center justify-center text-[0.8125rem] font-semibold text-Gray500">
3 following
Copy link

Choose a reason for hiding this comment

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

이런 부분도 겹치는 부분 같아서요!

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.

이번 과제하느라고 고생많으셨습니다! 추가 기능구현을 하신 부분이 인상깊습니다! 지원님의 노력이 많이 돋보였습니다~!

Comment on lines +64 to +79
className="ml-[4.35rem] mt-[0.31rem] h-0.5 w-[4.011rem] bg-Gray900 transition-transform duration-300"
style={{
transform:
currentTab === 0 ? "translateX(0)" : "translateX(10.65rem)",
}}
></div>
{/* <div>{tab[currentTab].content}</div> */}
<div className="w-full overflow-hidden">
<div
className="transition-transform duration-300"
style={{
transform: `translateX(-${currentTab * 50}%)`,
display: "flex",
width: "200%",
}}
>
Copy link

Choose a reason for hiding this comment

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

슬라이더 애니메이션으로 더 완성도 있어 보여요! framer motion이라는 라이브러리를 사용하면 애니메이션 사용이 더 편리해지더라구요! 참고부탁드려요

Comment on lines +88 to +98
<PictureIcon
onClick={handlePictureIconClick}
className="cursor-pointer"
/>
<input
type="file"
accept="image/*"
ref={fileInputRef}
onChange={handleImageUpload}
className="hidden"
/>
Copy link

Choose a reason for hiding this comment

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

저는 이번에 추가 기능 구현을 못했는데, 지원님은 이미지 전송기능 추가해주셨네요!! 👍

localStorage.setItem("pinnedChatIds", JSON.stringify(pinnedChatIds));
}, [pinnedChatIds]);

const handlePinToggle = (id: number | undefined) => {
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 handlePinToggle = (id: number | undefined) => {
const handlePinToggle = useCallback((id: number | undefined) => { ... }, []);

useCallback을 통한 함수 메모이제이션을 하면 최적화에 좋을 거 같습니다!

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.

5 participants