2024.04.19 TIL #채팅 #마지막_메세지_기억 #여기까지_읽었습니다
📌 Task TODOLIST
- [x] 마지막 메세지 저장하는 table 만들기
- [x] 컴포넌트 mount 시 이전 마지막 메세지 강조처리
- [x] 컴포넌트 unmount 시 마지막 메세지 기억
✨ 개발 내용
1. 마지막 메세지 저장하는 table 만들기
Why(이유)
기존 채팅과 관련된 messages와 chatting_room 이라는 2개의 table이 있었지만,
마지막 메세지는 유저마다 고유하고 다른 데이터이기 때문에 새로운 table을 생성하는 게 낫다는 판단
what(결과)
remember_last_msg라는 table을 만들고 구성요소와 각 type은 다음과 같이 구성했다.
2. 컴포넌트 mount 시 이전 마지막 메세지 강조처리
Why(이유)
마지막 메세지가 무엇인지 그 메세지에 배경색 입히기 + 하단에 문구 추가 + 해당 div까지 스크롤 하고 싶었다.
How(과정)
(1) useEffect 의 의존성 배열을 빈 배열로 하여 처음 mount 시, DB에 저장된 마지막 메세지의 id와 일치하는 메세지 div를 강조처리 한다.
(2) 강조처리를 위해서는 각 메세지의 참조가 필요했다. useRef로 messages를 초기값으로 넣은 뒤,
각 메세지 div에 ref값을 부여한다.
const lastDivRefs = useRef(messages);
const ChatList = ({ user, chatRoomId }: { user: User | null; chatRoomId: string }) => {
return (
<>
{messages &&
messages.map((msg, idx) => (
<div key={msg.message_id} className="w-full">
{isNextDay(idx, messages) ? (
<div className="flex justify-center my-[16px] bg-[#D4D4D8] mx-auto w-36 px-[16px] py-[6px] rounded-full text-white">
<p className="font-extralight tracking-wide text-sm">{showingDate(msg.created_at)}</p>
</div>
) : null}
{msg.send_from === user?.id ? (
<MyChat msg={msg} idx={idx} lastDivRefs={lastDivRefs} /> -----> *
)
const MyChat = ({ msg, idx, lastDivRefs }: { msg: Message; idx: number; lastDivRefs: any }) => {
return (
<>
// 각 div에 ref값 부여
<div id={msg.message_id} ref={lastDivRefs.current[idx]} className="flex gap-[8px] justify-end">
<div className="w-80 flex flex-col gap-1">
...
(3) 있으면 해당 메세지를 강조처리한다.
만약 마지막으로 읽은 메세지가 실제 마지막 메세지라면 강조처리 하지 않는다.
만약 내가 찾는 마지막 메세지의 id가 화면에 없다면 그냥 스크롤 다운.
// 여기까지 읽으셨습니다(처음 마운트 시에만 실행)
useEffect(() => {
const scrollBox = scrollRef.current;
if (scrollBox && messages) {
// DB에 마지막 메세지로 저장된 메세지와 id가 동일한 div 가 있다면 강조처리
const lastMsgValue = lastDivRefs?.current?.find((ref) => ref.message_id === lastMsgId);
const lastDiv = lastMsgValue && (lastMsgValue as any).current;
if (lastMsgId && lastMsgId !== messages[messages.length - 1].message_id && lastDiv) {
setLastCheckedDiv(lastDiv);
styleHere(lastDiv);
setIsScrolling(true);
} else {
// 그 외의 경우 기본적으로 스크롤 다운
scrollBox.scrollTop = scrollBox.scrollHeight;
setCheckedLastMsg(true);
}
}
}, [lastMsgId]);
const styleHere = (lastDiv: HTMLElement) => {
lastDiv.style.backgroundColor = '#F2EAFA';
lastDiv.style.borderRadius = '5px';
lastDiv.scrollIntoView({ block: 'center' });
};
🚨 TroubleShotting
(1) 처음 mount 시 여기까지 읽으셨습니다 강조처리를 보고, 내가 스크롤 다운을 하지도 않았는데 스크롤 다운이 되어버리는 이슈가 있었다.
Why(이유)
강조처리가 된 걸 보고 내가 스크롤다운 하는 그 사이의 시간을 잡아줄 state가 없었기 때문에, 강조처리와 스크롤 다운이 순차적으로 자동으로 실행된 것이 원인이었다.
How(과정)
checkedLastMsg 라는 state를 하나 만들어서, 여기까지 읽었습니다 강조처리를 확인했는지 판단하고,
스크롤 다운을 하는 그 사이 시점을 컨트롤 했다.
const [checkedLastMsg, setCheckedLastMsg] = useState(false);
// 스크롤 다운
useEffect(() => {
const scrollBox = scrollRef.current;
if (!isScrolling && messages) {
// 마지막으로 읽은 메세지(lastCheckedDiv)가 화면에 있을 때
if (lastCheckedDiv) {
// 강조처리를 보고난 뒤 스크롤을 맨 아래로 내리면 강조처리 해제
if (!checkedLastMsg) {
setCheckedLastMsg(true);
lastCheckedDiv.style.backgroundColor = '';
} else if (checkedLastMsg && prevMsgsLengthRef.current !== messages.length) {
// 강조처리를 보고나야만 타인으로부터 새로운 메세지가 추가되었을 때 스크롤 다운되도록
scrollBox.scrollTop = scrollBox.scrollHeight;
prevMsgsLengthRef.current = messages.length;
}
} else if (prevMsgsLengthRef.current !== messages.length || count === 0) {
// 이전 메세지가 화면에 없고 + 새로운 메세지가 추가되면 스크롤 다운이 따라가도록
scrollBox.scrollTop = scrollBox.scrollHeight;
prevMsgsLengthRef.current = messages.length;
}
}
}, [messages, isScrolling]);
📸 스크린샷
경우의 수 지옥이었다.....
아오 머리야..