본문 바로가기
프로젝트

[웹챗] 채팅 메시지 성능 이슈 해결 & 채팅 메시지 기본 UI

by ISA(류) 2021. 11. 30.

메시지 UI

ui의 경우 인라인 스타일로 위와 같이 간단히 잡았다. 지금은 먼저 간단히 본인이 보낸 메시지의 경우 토마토색(주황색)으로 다른 사람이 보낸 메시지의 경우 하얀색으로 배경을 통해서 표시 하지만 여러 사람이 동시에 채팅을 보낼 경우 각 유저의 고유한 식별 아이디나 프로필 같은 부분이 필요하다. 그외에는 기본적인 스타일 조금 더 가다듬고 사용자가 커스텀 할 수 있게 해주면 될거 같긴하다.

 

그리고 채팅창 인원목록 뿐 아니라 사용자 입출입 메시지 기능을 만들어 줘야할거 같다. 기존에 지나간 로그들을 다운로드 해서 보여주지 않기 때문에 채팅 연결이 끊기거나 재연결될때 해당 사용자는 이전의 메시지를 볼 수 없는게 서로간의 오해의 소지가 있어서 서로간의 연결 및 연결해제시 메시지를 보내는 기능이 필요하다.

 

SendMessage

    const sendMessage = (e: any) => {
        e.preventDefault();
        if (message.length > 0) {
            socket.emit('send', {
                socketIdx: socket.id,
                message: message,
                room: '#1'
            });
            setMessage('');
        }
    }

채팅 메시지 포맷의 경우 간단히 위와 같이 잡았다. 소켓아이디를 통해서 개개인을 식별하고, message를 통해서 메시지를 전송한다. room의 경우 추후 기능 추가에 따라서 여러 채팅방이나 1대1 채팅시 필요한 부분이고 해당 부분의 경우 상태관리 매니지먼트가 아닌 컨텍스트 api로 상태 관리를 하기로 결정했다. 이유는 특정 상태관리에 의존성을 가지는게 별로인거 같아서 그렇게 진행하기로 결정했다.

지난번에 서버로 전송된 메시지를 다시 클라이언트에 받는 부분에서 성능 이슈가 발생했는데 콘솔에 찍어서 확인해보니 메시지 전송시 마다 버퍼링인지 useState를 갱신하면서 컴포넌트에 포함된 receive이벤트가 중복해서 실행 되는걸 발견했다. 그게 갯수가 늘어나니 지속적으로 useState가 발생하면서 성능을 잡아먹는 것이였다. 처음에는 리렌더링에 의한 소켓 버퍼링 이슈로 잘못 진단하고 useRef를 통한 비제어 컴포넌트로 채팅 메시지를 리액트 컴포넌트에서 관리하는게 아닌 DOM으로 append해주는 방식으로 선회했었다.

const MessageNode = document.createElement('p');
MessageNode.innerText = msg;
const root: any = room.current;
root.appendChild(MessageNode);

그러나, 해당 방식의 경우 코드가 깔끔하지 못해서 맘에 안들어서 다시 곰곰히 생각하고 테스트 해본 결과 socket.on 이벤트 리스너 중복 등록이 근본 원인이라는 것을 디버깅 해냈고, 그래서 useEffect 훅을 통해서 useState를 통한 컴포넌트 리렌더링이 일어나더라도 receive 리스닝 이벤트 등록을 한번만 하도록 로직을 수정했다.

그로 인해서 성능 문제는 해결 되었지만 추가로 chatLog(채팅 메시지 배열)을 참조해서 갱신하는 로직에서 useEffect 내부의 receive 이벤트가 참조하는 chatLog가 갱신되지 않아서(클로저를 통해서 이전 컨텍스트를 유지하나보다) 따로 state라는 외부 변수를 만들어서 chatLog를 갱신해주는 방향으로 임시 조치해놨다. 이거보다 좋은 방법이 지금 당장은 떠오르지 않고(리덕스 사가 같은 것을 직접 만드는걸 제외하고) 큰 문제는 없을거 같아서 그렇게 처리했다.

전체 코드는 아래와 같다.

채팅창 코드

import React, { useState, useEffect } from 'react';
import { Socket } from 'socket.io-client';

interface ChatRoomProps {
    socket: Socket
}

const ChatRoom: React.FC<ChatRoomProps> = (props) => {
    const {
        socket
    } = props;
    const [chatLog, setChatLog] = useState<Array<any>>([]);
    const state = Array.from(chatLog);
    useEffect(() => { // 컴포넌트 리렌더링에 의한 리스닝 이벤트 중복 문제 해결
        socket.on('receive', (data: { idx: string, message: string }) => {
            if (socket.connected) {
                // 골때린다 컨텍스트가 갈리는건지 여기서는 chatLog가 갱신이 안된다.ㅋㅋ 비제어 방식
                state.push(data); 
                handleChatLog();
            }
        });
    }, []);
    const handleChatLog = () => {
        setChatLog([...state]);
    }

    return (
        <div
            className="chat-room"
            style={{
                display: "flex",
                flexFlow: "column nowrap",
                flex: "1 0px",
                border: "1px solid",
                overflow: "auto",
            }}
        >
            {
                chatLog.map((current, idx) => {
                    return (
                        <p
                            key={idx}
                            style={{
                                maxWidth: "88%",
                                padding: "12px",
                                margin: "8px",
                                borderRadius: "6px",
                                wordBreak: "break-all",
                                background: current.idx === socket.id ? "tomato" : "#ffffff",
                            }}
                        >
                            {current.message}
                        </p>
                    );
                })
            }
        </div>
    );
}

export default ChatRoom;

 

프론트 Repo

https://github.com/yoonjonglyu/webChat

 

GitHub - yoonjonglyu/webChat: 간단한 채팅 웹

간단한 채팅 웹. Contribute to yoonjonglyu/webChat development by creating an account on GitHub.

github.com

 

반응형