본문 바로가기
코드리뷰

[카카오톡 클론코딩]2022.08.03

by 치우치지않는 2022. 8. 3.

components > chatting > Content.tsx 

import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { MainContent } from '~/styles/BaseStyle';
import { CreateRoomRequest, RoomListDto } from '~/types/chatting';
import { UserData, UserResponseDto } from '~/types/user';
import { BASE_IMG_URL } from '~/constants';
import { findUserUsingId } from '~/apis/user';
import { Notification } from '~/styles/BaseStyle';
 
import 되는 것들. 눈에 띄는 것은 style 을 basestyle 에서 import 해 왓다는 것. 나는 항상 .css 파일로 스타일을 입혀왔기 때문에 이런 방식이 상당히 낯설지만 훨씬 효율적인 것 같다. 
 
export const MainContent = styled.section`
position: absolute;
top: 100px;
bottom: 5px;
left: 0px;
width: 100%;
overflow: auto;
& li {
position: relative;
padding: 20px 100px 20px 180px;
& img {
position: absolute;
top: 18px;
left: 120px;
width: 45px;
height: 45px;
border-radius: 15px;
cursor: pointer;
}
& p {
color: #707070;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-height: 19px;
font-size: 12px;
& b {
color: #000;
font-weight: bold;
font-size: 14px;
}
}
&:hover {
background-color: #eaeaeb;
}
}
`;

여기서 한 가지 의문이 드는 것은 왜 &li 안에 각종 img, p, b, hover 등의 요소를 넣었을까하는 것이다. 나는 늘 div 위주의 개발을 해왔어서 이런 방식이 많이 낯설다. position 설정도 나는 굳이 하지 않는데 여기서는 absolute 로 설정해 두었다. positoin 은 개념적인 공부가 부족하다고 생각되어 아래 링크를 보고 공부를 했다. 

https://www.daleseo.com/css-position-absolute/

 

[CSS] Absolute Position - 자유로운 엘리먼트 배치

Engineering Blog by Dale Seo

www.daleseo.com

 

components 를 읽으면서 일종의 규칙을 발견했다. 1. 각각의 component 에는 index.ts 파일이 있고 거기에는 무엇을 default 로 export 할지에 대한 정보가 적혀져 있다. 2. Header, Content, Footer + 기타 기능으로 파일들이 나뉘어져 작성되어 있다. 3. props 는 interface 로 코드 초입에 정리되어 있다. 아마도 타입스크립트의 특징인 듯 하다. 해당 인터페이스에는 변수명과 그에 대한 타입들이 적혀 있다. 4. 주로 기능들을 다루는 컴포넌트의 경우 export 가 여러 개 있고, Header, Content, Footer 는 하나의 export 로 디폴트 익스포트 컴포넌트를 정해 놓고 있다. 

 

components > chattingRoom > ChatBlock.tsx 는,

// 내가 보낸 채팅
// 다른 사람이 보낸 채팅
// 다른 사람이 보냈으며, 프로필 사진을 포함하는 채팅
2번째랑 3번째는 왜 구분했는지 알겠다. 같은 시간, 같은 분에 보내면 첫 번째 메시지만 프사랑 메시지가 같이 오고 그 밑에 따라오는 메시지는 프사 없이 내용만 오니까.
 
이렇게 세 가지를 export 하고 있는데 이 세 컴포넌트 모두 Chat 컴포넌트를 기반으로 동작한다. 따라서 여기서는 총 네 가지를 export 하는 중이다. 그리고 RightBlock 과 LeftBlock 으로 구분되어 있는데, 이는 내가 보낸 채팅은 오른쪽에 상대가 보낸 채팅은 왼쪽에 표시되게끔 하는 css 장치인 것 같다. 
 
span 이 많이 나오는데 잘 모르겠어서 아래 글을 참고해 공부해 보았다. 
 

<span> - HTML: Hypertext Markup Language | MDN

HTML <span> 요소는 구문 콘텐츠를 위한 통용 인라인 컨테이너로, 본질적으로는 아무것도 나타내지 않습니다. 스타일을 적용하기 위해서, 또는 lang 등 어떤 특성의 값을 서로 공유하는 요소를 묶을

developer.mozilla.org

사실상 div 랑 같은데 인라인 요소다 정도만 알아두고 넘어가면 될 것 같다. 

React.Fragment 코드가 자주 보이길래 찾아보았다. 의미 없는 div 사용을 없애기 위해 최상위 컴포넌트를 감싸는 코드라고 한다. <></>랑 똑같은 것이다. 

https://velog.io/@lilyoh/React-Fragments-%EC%82%AC%EC%9A%A9%EC%9D%B4%EC%9C%A0-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EB%B2%95

 

React Fragments 사용이유 및 사용법

리액트에서는 하나의 컴포넌트가 여러 개의 엘리먼트들을 반환한다. 리액트를 사용하기 위한 문법인 JSX 를 쓸 때, return 문 안에는 반드시 하나의 최상위 태그가 있어야 한다. 이는 리액트가 하

velog.io

내가 보낸 채팅에서

{content ? <SeparationBlock content={content} /> : null}
 
이 코드가 의미하는 게 무엇일까?  content 는 boolean 값이 아니라 Props 로 받아온 구조체인데..
<MyChat
msg={chat.message}
notRead={chat.not_read}
localeTime={time}
content={date}
key={chat.id}
/>
 
이 코드로 보면 props 로는 boolean 값을 받아오지 않는다. 그럼에도 불구하고 삼항 연산자를 쓸 수 있는 것일까? 이 코드가 의미하는 바는 무엇일까.. -> 내용이 있으면 경계를 생성하라의 의미인 것 같다. 일단 패스하고 넘어가자.
 
+ 저 코드에서 content 값이 참이면 이 블록에 props 로 content 값이 전달되고 채팅방에 경계가 생긴다.
// 날짜를 표시하는 등 채팅방의 경계를 나타냅니다.
export const SeparationBlock: React.FC<SeparationBlockProps> = ({
content
}) => {
return (
<BorderBlock>
<span>{content}</span>
</BorderBlock>
);
};

components > chattingRoom > Content.tsx

interface Props {
myId: number;
participant: Array<UserResponseDto>;
chattingList: Array<ChattingResponseDto>;
messageRef: React.RefObject<HTMLDivElement>;
showProfile(userData: UserResponseDto): void;
}
 
showProfile(userData: UserResponseDto): void;

이 부분에서 함수에는 값을 할당할 수가 없는 것이 아닌가.. 왜 void 를 할당햇는지..? 리턴값을 적어둔 것인지..?

Content.tsx 가 이번 플젝의 꽃인 듯하다. 제일 기능이 많고 복잡함. 큰 구조부터 잡으면 스타일은 간단하게 main 하나만 Wrapper 이용. Props 한 개와 Content 컴포넌트 한 개로 구성되어 있다. 그리고 export default Content 하고 있음. 그리고 그 Content 컴포넌트의 경우, 

const { myId, chattingList, participant, children, messageRef } = props;
const { showProfile } = props;
 
이렇게 받아온 props 를 저장하는 부분과 (여기서 왜 showProfile 은 따로 빼서 저장하는지??)
 
const renderChatting = chattingList.map((chat, idx) => {}
 
이렇게 채팅을 렌더링하는 부분으로 나뉘고, 여기 렌더링이 핵심임. 
 

Array.prototype.map() - JavaScript | MDN

map() 메서드는 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환합니다.

developer.mozilla.org

.map 은 지난 플젝에서 axios 로 데이터 넘겨 받으면서 써서 대강 순환시키는 함수라 정도만 알고 있었는데 인자가 함수인 경우는 어떤 식으로 굴러가는 것인지 궁금해서 위 자료를 조금 찾아봤다. 그래도 이해가 잘 안되는데.. 그럼 내용적으로 이해를 해 보자!

함수가 파라미터로 받아오는 것은 chat, idx이다. 이 두가지는 chattingList 에 있는데 chattingList 는 Array<ChattingResponseDto> 타입이다. 음? 그런데 여기 타입에는 chat, idx 가 없다.. 일단 오키. 

const createdAt = new Date(chat.createdAt);
const localeTime = createdAt.toLocaleTimeString();
const localeDate = createdAt.toLocaleDateString();
const removeSecond = localeTime.substring(0, localeTime.length - 3);
const senderId = chat.send_user_id;
 
chat 안에 send_user_id 가 있다. 이는 곧 ChattingRequestDto에 있는 것이다. 즉 울리는 chat = ChattingRequestDto 배열의 요소 중 한 개라고 볼 수 있겠다. 해결. 이제 idx 가 의미하는 것이 무엇인지를 찾으면 된다. 다시 chattingList 의 형식을 보면 아하. Array 즉 배열이다. 그렇다면 idx 는 이 배열의 index 를 뜻하는 것이라고 추론할 수 있다. 해결. 
 
정리하자면, renderChatting 은 ChattingResponseDto 를 배열 요소로 가지는 chattingList를 하나하나 훑어서 저장하고 있다. 

그리고 위 부분은 인자로 받아온 것들 중 시간이랑 senderId 를 별도의 변수에 저장해 두고 있다. 

const prevChat = idx >= 1 ? chattingList[idx - 1] : undefined;
const prevCreatedAt = prevChat ? new Date(prevChat.createdAt) : '';
const prevLocaleDate = prevCreatedAt
? prevCreatedAt.toLocaleDateString()
: '';
const prevLocaleTime = prevCreatedAt
? prevCreatedAt.toLocaleTimeString()
: '';
const prevRemoveSecond = prevLocaleTime
? prevLocaleTime.substring(0, prevLocaleTime.length - 3)
: '';
const isPrevSender = prevChat ? prevChat.send_user_id === senderId : false;
const isSameDate = prevLocaleDate === localeDate;
const sender = participant.find(
person => person.id === senderId
) as UserResponseDto;

prevChat 은 이 채팅 보내기 하나 전의 채팅이고 prevCreatedAt 은 prevChat 이 만들어진 시간을 기록하는 용도의 변수인 것 같다. 그리고 여기에서 파생되어 나온 것이 각각 날짜, 시간, 초를 저장하기 위한 prevLocale 시리즈이고. isPrevSender 의 경우 이전의 채팅을 보낸 sender 와 현재 채팅을 보내려는 sender 가 서로 같으면 true 를 다르면 false 를 저장하는 변수. isSameDate 는 설명할 필요 없을 것 같다. sender 에는 person 중에서 person.id 가 senderId 가 일치하는 사람을 찾은 다음 그 사람의 정보를 UserResponseDto 의 타입으로 저장하는 변수이다. 

 

// 채팅한 날짜를 표시
const getDate = () => {
let weekday = new Array(7);
weekday[0] = '일요일';
weekday[1] = '월요일';
weekday[2] = '화요일';
weekday[3] = '수요일';
weekday[4] = '목요일';
weekday[5] = '금요일';
weekday[6] = '토요일';
const splitDate = localeDate.split('.');
const day = weekday[createdAt.getDay()];
return `${splitDate[0].trim()}${splitDate[1].trim()}${splitDate[2].trim()}${day}`;
};
// 지금 채팅 날짜가 이전에 채팅 날짜와 다르면 날짜 표시
const date = isSameDate ? '' : getDate();

getDate 는 LocaleDate 를 이용해서 채팅을 보낸 것이 몇 년 몇 월 몇 일 무슨 요일인지를 문장으로 return 해 주는 함수이다. 

 

이제부터 처리할 작업은 경우 4가지에 따라 시간,날짜 등을 표시하는 것이다. 경우의 수는 다음의 네 가지이다.
1. 내가 보낸 채팅이고 시간, 날짜, 시간이 다르지 않은 경우 
2. 타인이 보낸 채팅이고 이전 채팅과 날짜, 시간이 다르지 않은 경우
3. 내가 보낸 채팅이고 시간, 날짜 등이 다른 경우
4. 타인이 보낸 채팅이고 시간, 날짜 등이 다르지 않은 경우

1번 경우

// 마지막 채팅인 경우
if (idx === chattingList.length - 1) {
// 내가 보낸 채팅인 경우
if (senderId === myId) {
return (
<MyChat
msg={chat.message}
notRead={chat.not_read}
localeTime={removeSecond}
content={date}
key={chat.id}
/>
);
}

2번 경우


// 이전에 보낸 채팅과 사람, 날짜가 동일한 경우
if (isPrevSender && isSameDate) {
return (
<FriendChat
msg={chat.message}
notRead={chat.not_read}
localeTime={removeSecond}
key={chat.id}
/>
);
}
 
이건 시간 상관없이 늘 return 하는 코드. 
return (
<FriendChatWithThumbnail
msg={chat.message}
user={sender}
notRead={chat.not_read}
localeTime={removeSecond}
content={date}
onImgClick={() => showProfile(sender)}
key={chat.id}
/>
);
}

3번 경우

// 내가 보낸 경우
if (senderId === myId) {
return (
<MyChat
msg={chat.message}
notRead={chat.not_read}
localeTime={time}
content={date}
key={chat.id}
/>
);
}

4번 경우

//이 부분은 다시 생각해 봐야 겠다. 아무래도 예외처리랑 헷갈린 듯!

'코드리뷰' 카테고리의 다른 글

[카카오톡 클론코딩]2022.08.07  (0) 2022.08.08
[카카오톡 클론코딩]2022.08.06  (0) 2022.08.07
[카카오톡 클론코딩]2022.08.05  (0) 2022.08.06
[카카오톡 클론코딩]2022.08.04  (0) 2022.08.04
코드리뷰 - 카카오톡  (0) 2022.07.30

댓글