본문 바로가기
개발 이야기/React

React 에서 WebSocket 활용하기 (feat. Typescript)

by 농개 2020. 9. 16.
반응형

React에서 Websocket 연결 및 간단 사용방법에 대해서 정리합니다.

 

서버단 코드는 따로 없습니다. 서버는 있다고 가정합니다. ^^:

 

React + Typescript 환경의 프론트 엔드 코드만 있어요. (웹 소켓 서버 구축은 검색해보면 많이 나와요...Pass)

 

 

01. CRA로 프로젝트 생성하기

create-react-app ws-example --typescript

위 같은 커맨드로 React 프로젝트를 생성합니다.

 

 

02. Package 구조

└─src
    │  App.css
    │  App.tsx
    │  index.tsx
    │
    ├─components
    │      Chatting.tsx
    │      TextInputBox.tsx
    │
    └─websocket
            WebSocketProvider.tsx

필요없는거 날리고 간단하게 위와 같이 잡아줬습니다.

 

 

03. App.tsx

import React from 'react';
import WebSocketProvider from './websocket/WebSocketProvider';
import Chatting from './components/Chatting';
import TextInputBox from './components/TextInputBox';

function App() {
  return (
    <div className="App">
      <WebSocketProvider>
        <Chatting />
        <TextInputBox />
      </WebSocketProvider>
    </div>
  );
}

export default App;

채팅 페이지와 같은 예제로 구성해보았습니다.

 

 

04. WebSocketProvider.tsx

 

import React, { useRef } from 'react';

const WebSocketContext = React.createContext<any>(null);
export { WebSocketContext };

export default ({ children }: { children: React.ReactNode }) => {
  const webSocketUrl = `ws://localhost:8080/ws`
  let ws = useRef<WebSocket | null>(null);

  if (!ws.current) {
    ws.current = new WebSocket(webSocketUrl);
    ws.current.onopen = () => {
      console.log("connected to " + webSocketUrl);
    }
    ws.current.onclose = error => {
      console.log("disconnect from " + webSocketUrl);
      console.log(error);
    };
    ws.current.onerror = error => {
      console.log("connection error " + webSocketUrl);
      console.log(error);
    };
  }

  return (
    <WebSocketContext.Provider value={ws}>
      {children}
    </WebSocketContext.Provider>
  );
}

가장 중요한 WebSocket 연결을 위한

WebSocketProvider 컴포넌트를 위와 같이 작성해줍니다.

React의 Context API를 활용해서 Context를 만들고 하위 컴포넌트로 ws(웹 소켓 객체)를 전달하기 위함입니다.

연결에 대한 WebSocket 이벤트 리스너인 onopen, onclose, onerror만 우선 만들어 주었습니다.

 

 

05. Chatting.tsx

import React, { useContext, useState } from 'react'
import { WebSocketContext } from '../websocket/WebSocketProvider';

function Chatting() {
  const ws = useContext(WebSocketContext);
  const [items, setItems] = useState<string[]>([]);

  const addItem = (item: string) => {
      setItems([
        ...items,
        item
      ]);
    };

  ws.current.onmessage = (evt: MessageEvent) => {
    const data = JSON.parse(evt.data) 
    addItem(data.chat);
  };

  return (
    <ul>
      {
        items.map((message) => {
          return (
            <li>{message}</li>
          )
        })
      }
    </ul>
  )
}

export default Chatting

채팅 목록을 보여주는 컴포넌트입니다.

여기선 간단히 웹소켓 onmessage 이벤트가 발생하면(즉, 웹소켓을 통해 메시지를 수신하면)

채팅 메시지를 목록에 추가 해주는 작업만했습니다.(실제 채팅앱이라면 보낸사람, 날짜 등 많은 정보들이 포함 되겠죠?)

 

 

 

06. TextInputBox.tsx

import React, { useState, useContext } from 'react'
import { WebSocketContext } from '../websocket/WebSocketProvider';

function TextInputBox() {
  const [message, setMessage] = useState("");
  const ws = useContext(WebSocketContext);

  const handleChangeText = (e: any) => {
    setMessage(e.target.value);
  }

  const handleClickSubmit = () => {
    ws.current.send(JSON.stringify({
      chat: message
    }))

    setMessage('');
  }

  return (
    <div>
      <input type="text" value={message} onChange={handleChangeText}></input>
      <button type="button" onClick={handleClickSubmit}>Send!</button>
    </div>
  )
}

export default TextInputBox

TextInputBox 컴포넌트는 채팅 매시지 입력란 입니다.

버튼 클릭 시, Provider로 부터 받은 ws 객체를 통해 웹소켓 서버로 send를 날립니다.

 

 

** 주의 할점 **

위 예제는 WebsocketProvider를 통해 하위 컴포넌트에서 ws객체에 접근 하는 방법입니다.

만약 여러 컴포넌트에서 ws 객체를 받아 사용할 때 onmessage 이벤트 리스너를 중복해서 작성해준다면...

가장 마지막에 작성된 onmessage 이벤트 리스너만 동작하게될겁니다...

onmessage의 경우 위에서 소개한 방법보다는 redux를 통해 관리하는 것이 어쩌면 수월할지도 모르겠네요

 

예를들면, 아래와 같이 말입니다. (redux-saga를 쓴다면)

// WebSocketProvider.tsx
import React, { useRef } from 'react';

const WebSocketContext = React.createContext<any>(null);
export { WebSocketContext };

export default ({ children }: { children: React.ReactNode }) => {
  const webSocketUrl = `ws://localhost:8080/ws`
  let ws = useRef<WebSocket | null>(null);

  if (!ws.current) {
    ws.current = new WebSocket(webSocketUrl);
    // (중략)
    ws.current.onmessage = (evt: MessageEvent) => {
      const data = JSON.parse(evt.data);
      
      dispatch({ type: ON_MESSAGE_SUCCESS, payload: data })
    };
  }

  return (
    <WebSocketContext.Provider value={ws}>
      {children}
    </WebSocketContext.Provider>
  );
}

 

// saga

function* messageHandler(action: websocket.ActionType){
  try{
    const message = action.payload; // 웹소켓 수신 메시지
    
    switch(message.messageType){
      case 'JOIN':
        // Something..
        break;
      case 'LEAVE':
        // Something..
        break;
      case 'CHAT':
        // Something..
        break;

...(중략)

 

 

반응형