WebSocket и real-time
Для чатов, уведомлений, live-обновлений.
WebSocket API
const ws = new WebSocket('wss://api.example.com/ws');
ws.onopen = () => console.log('соединение открыто');
ws.onmessage = (e) => console.log('сообщение:', e.data);
ws.onerror = (e) => console.error('ошибка:', e);
ws.onclose = () => console.log('соединение закрыто');
ws.send(JSON.stringify({ type: 'subscribe', channel: 'chat' }));
ws.close();
Хук useWebSocket
function useWebSocket(url: string) {
const [messages, setMessages] = useState<unknown[]>([]);
const wsRef = useRef<WebSocket | null>(null);
useEffect(() => {
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onmessage = (e) => {
setMessages((m) => [...m, JSON.parse(e.data)]);
};
return () => ws.close();
}, [url]);
const send = useCallback((data: unknown) => {
wsRef.current?.send(JSON.stringify(data));
}, []);
return { messages, send };
}
Переподключение
Сеть пропадает — нужно переподключаться:
function useReconnectingWebSocket(url: string) {
const wsRef = useRef<WebSocket | null>(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
let retry = 0;
let timeout: ReturnType<typeof setTimeout>;
function connect() {
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => {
setConnected(true);
retry = 0;
};
ws.onclose = () => {
setConnected(false);
const delay = Math.min(1000 * 2 ** retry, 30000);
retry++;
timeout = setTimeout(connect, delay);
};
ws.onerror = () => ws.close();
}
connect();
return () => {
clearTimeout(timeout);
wsRef.current?.close();
};
}, [url]);
return { connected, ws: wsRef.current };
}
Экспоненциальная задержка: 1s, 2s, 4s, 8s, до 30s.
При фокусе и фоне
Соединение стоит закрывать в фоне и открывать при возврате:
import { AppState } from 'react-native';
useEffect(() => {
const sub = AppState.addEventListener('change', (state) => {
if (state === 'active') connect();
if (state === 'background') disconnect();
});
return () => sub.remove();
}, []);
Socket.IO
npm install socket.io-client
import { io } from 'socket.io-client';
const socket = io('https://api.example.com', {
auth: { token },
transports: ['websocket'],
});
socket.on('connect', () => console.log('connected'));
socket.on('message', (data) => console.log(data));
socket.emit('join', { room: 'chat' });
socket.disconnect();
Переподключение, комнаты, события — из коробки.
Laravel Echo + Reverb
Если бэкенд на Laravel:
npm install laravel-echo pusher-js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
const echo = new Echo({
broadcaster: 'reverb',
key: process.env.EXPO_PUBLIC_REVERB_KEY,
wsHost: process.env.EXPO_PUBLIC_REVERB_HOST,
wsPort: 443,
forceTLS: true,
authEndpoint: 'https://api.example.com/broadcasting/auth',
auth: { headers: { Authorization: `Bearer ${token}` } },
});
echo.private(`chat.${roomId}`)
.listen('MessageSent', (e) => {
setMessages((m) => [...m, e.message]);
});
Интеграция с TanStack Query
Обновление кеша из WebSocket:
useEffect(() => {
const handler = (data: Message) => {
queryClient.setQueryData(['messages', data.roomId], (old: Message[]) => [...(old ?? []), data]);
};
ws.on('message', handler);
return () => ws.off('message', handler);
}, [queryClient]);
Индикатор соединения
function ChatScreen() {
const { connected } = useReconnectingWebSocket(url);
return (
<>
{!connected && (
<View style={styles.banner}>
<Text>Соединение потеряно, переподключение...</Text>
</View>
)}
{/* чат */}
</View>
);
}
Очередь сообщений офлайн
const [queue, setQueue] = useState<Message[]>([]);
useEffect(() => {
if (connected && queue.length > 0) {
queue.forEach((msg) => ws.send(JSON.stringify(msg)));
setQueue([]);
}
}, [connected, queue]);
Итоги
- WebSocket для real-time
- Переподключение с задержкой
- AppState для фона
- Socket.IO упрощает работу
- Laravel Echo для Laravel
- Интеграция с TanStack Query
- Индикатор соединения