TIL

2024.04.19 TIL #채팅 #마지막_메세지_기억 #여기까지_읽었습니다

inz1234 2024. 5. 9. 23:32

📌 Task TODOLIST

- [x] 마지막 메세지 저장하는 table 만들기

- [x] 컴포넌트 mount 시 이전 마지막 메세지 강조처리

- [x] 컴포넌트 unmount 시 마지막 메세지 기억


개발 내용

1. 마지막 메세지 저장하는 table 만들기

Why(이유)

기존 채팅과 관련된 messageschatting_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]);

 

📸 스크린샷

 

경우의 수 지옥이었다.....

아오 머리야..