[Spring boot + React] STOMP로 실시간 채팅 구현하기 (2) - React 클라이언트 구현하기

클롱바·2022년 9월 7일
5
post-custom-banner

React로 STOMP 클라이언트 구현

실제 프로젝트에 사용됐던 코드 중 간소화를 많이 시켰기 때문에 오류가 발생할 수도 있으며,참고용으로만 사용하길 바람

실시간 채팅을 위한 클라이언트 구현

0. 모듈 설치

npm을 통해 STOMP 모듈을 설치한다.

npm i @stomp/stompjs

1. 소켓 프록시 설정

웹 소켓을 사용해서 서버와 통신하려면 http가 아니라 ws 프로토콜을 사용해야 하므로 소켓 프록시를 따로 설정한다.

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = (app) => {
    app.use(
        "/ws",
        createProxyMiddleware({ target: "http://localhost:8787", ws: true })
    );
};
  • 옵션으로 ws: true를 줘서 웹 소켓을 사용한다.

2. 클라이언트 생성 코드 작성

  1. 웹소켓에 연결할 때 STOMP 클라이언트를 생성하는 코드를 작성한다.
function CreateReadChat() {
  const client = useRef({});

  const connect = () => { // 연결할 때
    client.current = new StompJs.Client({
      brokerURL: 'ws://localhost:8787/ws',
      onConnect: () => {
        subscribe(); // 연결 성공 시 구독하는 로직 실행
      },
    );
    client.current.activate(); // 클라이언트 활성화
  };
  
  const disconnect = () => { // 연결이 끊겼을 때 
    client.current.deactivate();
  };
  
  useEffect(() => {
    connect();
    
    return () => disconnect();
  }, []);
  • useRef() 훅을 사용해 속성 값이 변경돼도 재렌더링하지 않고, 다시 렌더링하더라도 값이 유실되지 않도록 클라이언트를 current 속성에 만든다.
  • onConnect() 옵션에서 연결에 성공했을 시, 채널을 구독하고 메시지를 받는 함수를 실행한다.
  • 연결에 성공해서 클라이언트가 정상적으로 만들어지면 activate() 함수를 사용해 클라이언트를 활성화 시킨다.
  • useEffect() 훅을 사용해서 최초 렌더링 시, 웹소켓에 연결되도록 한다.
    • 의존성을 빈 배열로 줘서 connect()가 한 번만 실행되도록 한다.
  1. 채널을 구독하고, 구독 중인 채널에서 메시지가 왔을 때 처리하는 코드를 작성한다.
  const [chatList, setChatList] = useState([]); // 화면에 표시될 채팅 기록
  const { apply_id } = useParams(); // 채널을 구분하는 식별자를 URL 파라미터로 받는다.

  const subscribe = () => {
    client.current.subscribe('/sub/chat/' + apply_id, (body) => {
      const json_body = JSON.parse(body.body);
      setChatList((_chat_list) => [
        ..._chat_list, json_body
      ]);
    });
  };
  • 메시지의 payload는 body.body 에 실려온다.
    • string으로 오기 때문에 JSON으로 사용하려면 파싱이 한 번 필요하다.
  • 메시지를 받으면 setChatList로 화면에 받은 메시지를 표시한다.
  1. 메시지를 발행하는 코드를 작성한다.
  const [chat, setChat] = useState(''); // 입력되는 채팅

  const publish = (chat) => {
    if (!client.current.connected) return; // 연결되지 않았으면 메시지를 보내지 않는다. 

    client.current.publish({
      destination: '/pub/chat',
      body: JSON.stringify({
        applyId: apply_id,
        chat: chat,
      }), // 형식에 맞게 수정해서 보내야 함.
    });

    setChat('');
  };
  • destination은 STOMP 서버에서 메시지를 받기 위해 @MessageMapping 으로 연결해둔 주소로 설정한다.
  • body는 STOMP 서버에서 정의하고 있는 형식에 맞게 가공해서 보낸다.
  • 메시지를 보내면 setChat('');을 통해 메시지 입력란을 초기화한다.

4.전체적인 코드는 다음과 같아진다.

import { useRef, useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import * as StompJs from '@stomp/stompjs';
import instance from '../../utils/axiosConfig';

function CreateReadChat() {
  const [chatList, setChatList] = useState([]);
  const [chat, setChat] = useState('');

  const { apply_id } = useParams();
  const client = useRef({});

  const connect = () => {
    client.current = new StompJs.Client({
      brokerURL: 'ws://localhost:8787/ws',
      onConnect: () => {
        console.log('success');
        subscribe();
      },
    });
    client.current.activate();
  };

  const publish = (chat) => {
    if (!client.current.connected) return;

    client.current.publish({
      destination: '/pub/chat',
      body: JSON.stringify({
        applyId: apply_id,
        chat: chat,
      }),
    });

    setChat('');
  };

  const subscribe = () => {
    client.current.subscribe('/sub/chat/' + apply_id, (body) => {
      const json_body = JSON.parse(body.body);
      setChatList((_chat_list) => [
        ..._chat_list, json_body
      ]);
    });
  };

  const disconnect = () => {
    client.current.deactivate();
  };

  const handleChange = (event) => { // 채팅 입력 시 state에 값 설정
    setChat(event.target.value);
  };

  const handleSubmit = (event, chat) => { // 보내기 버튼 눌렀을 때 publish
    event.preventDefault();

    publish(chat);
  };
  
  useEffect(() => {
    connect();

    return () => disconnect();
  }, []);

  return (
    <div>
      <div className={'chat-list'}>{chatList}</div>
      <form onSubmit={(event) => handleSubmit(event, chat)}>
        <div>
          <input type={'text'} name={'chatInput'} onChange={handleChange} value={chat} />
        </div>
        <input type={'submit'} value={'의견 보내기'} />
      </form>
    </div>
  );
}

참고

profile
백엔드 개발자 지망생
post-custom-banner

0개의 댓글