React와 WebSocket으로 실시간 기능 구현
React 환경에서 WebSocket과 socket.io를 활용한 실시간 통신 구조와 클라이언트·서버 예제, 구현 흐름과 주의사항을 정리
목차
개요
웹 애플리케이션에 실시간 기능을 추가하면 사용자 경험이 크게 향상된다. 이 글은 react websocket 사용법을 중심으로 socket.io를 연동해 실시간 업데이트 react 환경에서 작동하게 만드는 과정을 설명한다. 처음 접하는 개발자도 흐름을 이해할 수 있도록 개념, 설치, 서버 코드, React 클라이언트 코드, 주의사항 순으로 정리한다.
WebSocket과 Socket.IO 차이
WebSocket은 브라우저와 서버 간 양방향 통신을 제공하는 표준 프로토콜이다. 반면 socket.io는 여러 전송 방식과 자동 재연결, 네임스페이스, 룸 같은 편의 기능을 추가한 라이브러리다. 즉 기본은 WebSocket이나, 실무에서는 안정성과 편리함 때문에 socket.io를 함께 쓰는 경우가 많다.
언제 socket.io를 선택할까
- 브라우저 호환성 문제를 최소화하려는 경우
- 자동 재연결과 이벤트 기반 통신이 필요할 경우
- 룸 기반 메시징이나 인증 흐름을 간단히 처리하려는 경우
환경 준비
간단한 예제로 Node.js 서버와 React 클라이언트를 만든다. 필요한 패키지는 다음과 같다.
- 서버: express, socket.io
- 클라이언트: react, socket.io-client
서버 코드 예제
간단한 브로드캐스트 채팅 서버 코드다. 클라이언트가 보낸 메시지를 모두에게 전송한다.
const express = require('express')
const http = require('http')
const { Server } = require('socket.io')
const app = express()
const server = http.createServer(app)
const io = new Server(server, { cors: { origin: '*' } })
io.on('connection', socket => {
console.log('client connected:', socket.id)
socket.on('message', data => {
// 받은 메시지를 모든 클라이언트에 전송
io.emit('message', data)
})
socket.on('disconnect', reason => {
console.log('client disconnected:', socket.id, reason)
})
})
server.listen(3000, () => {
console.log('Server listening on port 3000')
})
React 클라이언트 예제
React 측에서는 socket.io-client를 사용해 서버와 연결한다. 연결은 보통 최상위 컴포넌트나 채팅 컴포넌트에서 수행한다.
import React, { useEffect, useRef, useState } from 'react'
import { io } from 'socket.io-client'
export default function Chat() {
const [messages, setMessages] = useState([])
const [input, setInput] = useState('')
const socketRef = useRef(null)
useEffect(() => {
// 서버 주소는 환경에 맞게 변경
socketRef.current = io('http://localhost:3000')
socketRef.current.on('connect', () => {
console.log('connected', socketRef.current.id)
})
socketRef.current.on('message', msg => {
setMessages(prev => [...prev, msg])
})
socketRef.current.on('disconnect', () => {
console.log('disconnected')
})
return () => {
socketRef.current.disconnect()
}
}, [])
const sendMessage = () => {
if (!input) return
const payload = { text: input, time: Date.now() }
socketRef.current.emit('message', payload)
setInput('')
}
return (
<div>
<h3>채팅</h3>
<ul>
{messages.map((m, i) => (
<li key={i}>{m.text} - {new Date(m.time).toLocaleTimeString()}</li>
))}
</ul>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={sendMessage}>전송</button>
</div>
)
}
핵심 흐름 설명
- 클라이언트가 socket.io로 서버에 연결한다.
- 서버는 connection 이벤트로 소켓을 관리한다.
- 클라이언트가 메시지를 emit하면 서버는 해당 이벤트를 받아 처리 후 방송한다.
- 클라이언트는 message 이벤트를 수신해 UI를 업데이트한다.
주의사항과 최적화
실제 서비스에서는 다음 항목을 고려해야 한다.
- 인증: 연결 시 토큰 검증으로 불법 접근을 차단한다.
- 스케일링: 여러 인스턴스에서 socket.io는 Redis 어댑터 등으로 메시지 동기화가 필요하다.
- 재연결 정책: 네트워크 불안정에 대비한 재시도 전략을 적용한다.
- 에러 처리: 이벤트별 에러 핸들링과 로그가 필수다.
- 리소스 관리: 오래된 소켓 정리와 메모리 누수 점검.
실시간 업데이트와 성능
실시간 업데이트 react 환경에서는 불필요한 리렌더를 줄이는 것이 중요하다. 메시지 배열이 커지면 가상화(virtualization) 적용을 검토한다. 또한 빈번한 이벤트는 쓰로틀링이나 배치 처리로 묶어서 처리하면 네트워크와 렌더 측 부담을 낮출 수 있다.
결론
react websocket 사용법은 개념을 이해하고 작은 예제를 통해 손에 익히는 것이 빠른 접근 방법이다. socket.io react 예제는 자동 재연결과 이벤트 중심의 구조 덕분에 초기에 구현하기 좋다. 실제 서비스에서는 인증, 스케일링, 에러 처리를 함께 고려해야 안정적인 실시간 기능을 운영할 수 있다.