Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Работа с WebSocket в React
WebSocket — это протокол для двусторонней коммуникации между клиентом и сервером. В отличие от HTTP, WebSocket позволяет серверу отправлять данные клиенту без явного запроса. Это идеально для real-time приложений: чат, уведомления, игры, live обновления.
Основы WebSocket API
// Подключение к WebSocket серверу
const ws = new WebSocket('ws://localhost:8080');
// События жизненного цикла
ws.addEventListener('open', (event) => {
console.log('Connected');
ws.send('Hello Server!');
});
ws.addEventListener('message', (event) => {
console.log('Message from server:', event.data);
});
ws.addEventListener('error', (event) => {
console.error('WebSocket error:', event);
});
ws.addEventListener('close', (event) => {
console.log('Disconnected');
});
// Отправка данных
ws.send('Some message');
// Закрытие соединения
ws.close();
Решение 1: Простой хук useWebSocket
Создаём кастомный хук для управления WebSocket подключением:
import { useState, useEffect, useRef, useCallback } from 'react';
function useWebSocket(url, options = {}) {
const [state, setState] = useState({
data: null,
error: null,
isConnected: false
});
const wsRef = useRef(null);
const reconnectAttemptsRef = useRef(0);
const maxReconnectAttempts = options.maxReconnectAttempts || 5;
const connect = useCallback(() => {
try {
const ws = new WebSocket(url);
ws.onopen = () => {
setState(prev => ({ ...prev, isConnected: true, error: null }));
reconnectAttemptsRef.current = 0;
console.log('WebSocket connected');
};
ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
setState(prev => ({ ...prev, data: message }));
} catch {
setState(prev => ({ ...prev, data: event.data }));
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
setState(prev => ({ ...prev, error: error.message }));
};
ws.onclose = () => {
setState(prev => ({ ...prev, isConnected: false }));
// Автоматическое переподключение
if (reconnectAttemptsRef.current < maxReconnectAttempts) {
reconnectAttemptsRef.current++;
const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 30000);
setTimeout(() => {
console.log(`Reconnecting... Attempt ${reconnectAttemptsRef.current}`);
connect();
}, delay);
}
};
wsRef.current = ws;
} catch (error) {
setState(prev => ({ ...prev, error: error.message }));
}
}, [url, maxReconnectAttempts]);
const send = useCallback((message) => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify(message));
} else {
console.warn('WebSocket is not connected');
}
}, []);
const disconnect = useCallback(() => {
if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
}, []);
useEffect(() => {
connect();
return () => {
disconnect();
};
}, [connect, disconnect]);
return {
...state,
send,
disconnect
};
}
Использование:
function ChatComponent() {
const { data, isConnected, error, send } = useWebSocket('ws://localhost:8080');
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
useEffect(() => {
if (data) {
setMessages(prev => [...prev, data]);
}
}, [data]);
const handleSend = (e) => {
e.preventDefault();
if (input.trim()) {
send({ type: 'message', content: input });
setInput('');
}
};
return (
<div>
<div>Status: {isConnected ? 'Connected' : 'Disconnected'}</div>
{error && <div className="error">{error}</div>}
<div className="messages">
{messages.map((msg, idx) => (
<div key={idx}>{msg.content}</div>
))}
</div>
<form onSubmit={handleSend}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message"
disabled={!isConnected}
/>
<button type="submit" disabled={!isConnected}>
Send
</button>
</form>
</div>
);
}
Решение 2: Использование Context для глобального подключения
Если несколько компонентов нужно подключены к одному WebSocket:
import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
const WebSocketContext = createContext();
function WebSocketProvider({ url, children }) {
const [isConnected, setIsConnected] = useState(false);
const wsRef = useRef(null);
const [listeners, setListeners] = useState({});
useEffect(() => {
const ws = new WebSocket(url);
ws.onopen = () => setIsConnected(true);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
const { type } = data;
if (listeners[type]) {
listeners[type].forEach(cb => cb(data));
}
};
ws.onclose = () => setIsConnected(false);
wsRef.current = ws;
return () => ws.close();
}, [url, listeners]);
const send = (message) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify(message));
}
};
const on = (type, callback) => {
setListeners(prev => ({
...prev,
[type]: [...(prev[type] || []), callback]
}));
};
const off = (type, callback) => {
setListeners(prev => ({
...prev,
[type]: (prev[type] || []).filter(cb => cb !== callback)
}));
};
return (
<WebSocketContext.Provider value={{ isConnected, send, on, off }}>
{children}
</WebSocketContext.Provider>
);
}
function useWebSocketContext() {
return useContext(WebSocketContext);
}
Использование:
function NotificationComponent() {
const { isConnected, on } = useWebSocketContext();
const [notifications, setNotifications] = useState([]);
useEffect(() => {
const handleNotification = (data) => {
setNotifications(prev => [...prev, data.message]);
};
on('notification', handleNotification);
// Cleanup
return () => {
// Удалить слушателя
};
}, [on]);
return (
<div>
{notifications.map((notif, idx) => (
<div key={idx}>{notif}</div>
))}
</div>
);
}
function App() {
return (
<WebSocketProvider url="ws://localhost:8080">
<ChatComponent />
<NotificationComponent />
</WebSocketProvider>
);
}
Решение 3: Использование библиотеки socket.io
Socket.IO — популярная библиотека с fallback'ами для старых браузеров:
import io from 'socket.io-client';
import { useEffect, useState, useRef } from 'react';
function useSocket(url) {
const [isConnected, setIsConnected] = useState(false);
const [data, setData] = useState(null);
const socketRef = useRef(null);
useEffect(() => {
const socket = io(url, {
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: 5
});
socket.on('connect', () => setIsConnected(true));
socket.on('disconnect', () => setIsConnected(false));
socket.on('message', setData);
socketRef.current = socket;
return () => socket.disconnect();
}, [url]);
const emit = (event, data) => {
socketRef.current?.emit(event, data);
};
return { isConnected, data, emit };
}
Практический пример: Live Chat
function LiveChat() {
const { data, isConnected, send } = useWebSocket('ws://localhost:8080');
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState([]);
const [input, setInput] = useState('');
const messagesEndRef = useRef(null);
// Обработка входящих сообщений
useEffect(() => {
if (!data) return;
if (data.type === 'message') {
setMessages(prev => [...prev, data]);
} else if (data.type === 'user_joined') {
setUsers(prev => [...prev, data.user]);
} else if (data.type === 'user_left') {
setUsers(prev => prev.filter(u => u.id !== data.userId));
}
}, [data]);
// Автоскролл к последнему сообщению
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const handleSend = () => {
if (input.trim() && isConnected) {
send({
type: 'message',
text: input,
timestamp: new Date().toISOString()
});
setInput('');
}
};
return (
<div className="chat-container">
<div className="status">
{isConnected ? 'Connected' : 'Disconnected'}
</div>
<div className="messages">
{messages.map((msg, idx) => (
<div key={idx} className="message">
<strong>{msg.user}</strong>: {msg.text}
</div>
))}
<div ref={messagesEndRef} />
</div>
<div className="input-area">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
placeholder="Type a message..."
/>
<button onClick={handleSend} disabled={!isConnected}>
Send
</button>
</div>
<div className="users">
<h3>Users Online ({users.length})</h3>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
</div>
);
}
Обработка ошибок и восстановления
function useRobustWebSocket(url, options = {}) {
const [state, setState] = useState({
isConnected: false,
lastMessage: null,
error: null,
retryCount: 0
});
const wsRef = useRef(null);
const retryTimeoutRef = useRef(null);
const connect = useCallback(() => {
try {
const ws = new WebSocket(url);
ws.onopen = () => {
setState(prev => ({
...prev,
isConnected: true,
error: null,
retryCount: 0
}));
};
ws.onmessage = (event) => {
setState(prev => ({
...prev,
lastMessage: JSON.parse(event.data)
}));
};
ws.onerror = () => {
setState(prev => ({
...prev,
error: 'Connection failed',
isConnected: false
}));
};
ws.onclose = () => {
setState(prev => ({
...prev,
isConnected: false
}));
// Экспоненциальная задержка перед переподключением
if (state.retryCount < (options.maxRetries || 5)) {
const delay = Math.pow(2, state.retryCount) * 1000;
retryTimeoutRef.current = setTimeout(connect, delay);
setState(prev => ({
...prev,
retryCount: prev.retryCount + 1
}));
}
};
wsRef.current = ws;
} catch (error) {
setState(prev => ({
...prev,
error: error.message
}));
}
}, [url, state.retryCount, options]);
useEffect(() => {
connect();
return () => {
if (retryTimeoutRef.current) {
clearTimeout(retryTimeoutRef.current);
}
wsRef.current?.close();
};
}, [connect]);
return state;
}
Выводы
- WebSocket для двусторонней real-time коммуникации
- Создавай кастомные хуки для управления подключением
- Обработай переподключение и ошибки
- Используй Context для общего состояния несколькими компонентами
- socket.io предоставляет fallback'и для старых браузеров
- Правильно очищай соединение при размонтировании компонента
- Не забывай обработку сбоев сети и graceful degradation