Как правильно инициализировать соединение по websocket в компоненте react?
Предыстория: я backend разработчик, для своего проекта написал фронт на реакт. Все работает, но меня немного смущает одна вещь: один из компонентов использует подключение по websocket, и в консоли разработчика вижу что подключение по ws инициализируется сразу при монтировании радительского компонента, что логично т.к. в дочернем компоненте объект ws объявлен до функции по умолчанию.
Чего хочу: чтобы инстанс ws создавался при монтировании дочернего компонента, а при размонтировании корректно завершал соединение вызовом .close() и так же при монтировании компонента нужно по этому же вебсокету ровно один раз при каждом монтировании (сейчас у меня просто ws.send() в useEffect с пустыми зависимостями) посылать одно сообщение.
Код на котором проводил эксперименты:
import { useState, useRef, useEffect } from 'react'
// Родительский компонент
function App() {
const [showChild, setShowChild] = useState(false);
return (
<div>
<button onClick={() => setShowChild(!showChild)}>
{showChild ? 'Убрать дочерний компонент' : 'Показать дочерний компонент'}
</button>
{showChild && <ChildComponent />}
</div>
);
}
// Дочерний компонент
function ChildComponent() {
const [message, setMessage] = useState('');
const [inputValue, setInputValue] = useState('');
const socketRef = useRef(null);
useEffect(() => {
// Инициализация WebSocket при монтировании компонента
socketRef.current = new WebSocket('ws://your-backend-url');
// Отправка сообщения "привет" при открытии соединения
socketRef.current.onopen = () => {
socketRef.current.send('привет');
};
// Обработка входящих сообщений
socketRef.current.onmessage = (event) => {
setMessage(event.data);
};
// Обработка ошибок
socketRef.current.onerror = (error) => {
console.error('WebSocket error:', error);
setMessage('Ошибка соединения');
};
// Закрытие соединения при размонтировании компонента
return () => {
if (socketRef.current) {
socketRef.current.close();
}
};
}, []);
const handleSendMessage = () => {
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
socketRef.current.send(inputValue);
setInputValue('');
}
};
return (
<div style={{ marginTop: '20px', border: '1px solid #ccc', padding: '10px' }}>
<div>Полученное сообщение: <strong>{message}</strong></div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Введите сообщение"
/>
<button onClick={handleSendMessage}>Отправить</button>
</div>
);
}
export default App
но вебсокет закрывается до установления соединения, а если убрать close из useEffect не понимаю как завершить корректно.
Просьба подсказать что не так делаю.
Ответы (1 шт):
по итогу дело было в strict mode, который перерисовывает компоненты несколько раз. минимально рабочий код в build:
import { useState, useRef, useEffect } from 'react'
// Родительский компонент
function App() {
const [showChild, setShowChild] = useState(false);
return (
<div>
<button onClick={() => setShowChild(!showChild)}>
{showChild ? 'Убрать дочерний компонент' : 'Показать дочерний компонент'}
</button>
{showChild && <ChildComponent />}
</div>
);
}
// Дочерний компонент
function ChildComponent() {
const [message, setMessage] = useState('');
const [inputValue, setInputValue] = useState('');
const socketRef = useRef(null);
useEffect(() => {
// Инициализация WebSocket при монтировании компонента
socketRef.current = new WebSocket('wss://echo.websocket.org/');
// Отправка сообщения "привет" при открытии соединения
socketRef.current.onopen = () => {
socketRef.current.send('привет');
};
// Обработка входящих сообщений
socketRef.current.onmessage = (event) => {
setMessage(event.data);
};
// Обработка ошибок
socketRef.current.onerror = (error) => {
console.error('WebSocket error:', error);
setMessage('Ошибка соединения');
};
// Закрытие соединения при размонтировании компонента
return () => {
if (socketRef.current) {
socketRef.current.close(1000);
}
};
}, []);
const handleSendMessage = () => {
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
socketRef.current.send(inputValue);
setInputValue('');
}
};
return (
<div style={{ marginTop: '20px', border: '1px solid #ccc', padding: '10px' }}>
<div>Полученное сообщение: <strong>{message}</strong></div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Введите сообщение"
/>
<button onClick={handleSendMessage}>Отправить</button>
</div>
);
}
export default App
Этот код делает именно то, что мне нужно в части работы с websocket