> Как построить приложение на React и Redux для обработки данных из веб-сокетов и отображения на UI (React)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Стек: React

> Пример ответа

Короткий ответ

Для обработки данных из веб-сокетов в React + Redux создаётся middleware, который подключается к WebSocket при старте приложения, диспатчит экшены при получении сообщений и обрабатывает reconnect. Компоненты подписываются на обновлённый стор через useSelector. Важно управлять жизненным циклом соединения: открывать в middleware или в эффекте корневого компонента, закрывать при unmount. Для оптимизации рендера используется createSelector и debounce на частые обновления.

Подробное объяснение

Архитектура строится на трёх уровнях: middleware для работы с WebSocket, Redux store для хранения данных и React-компоненты для отображения. Middleware перехватывает экшены типа WS_CONNECT, WS_DISCONNECT, WS_SEND и управляет соединением. При получении сообщения middleware парсит JSON и диспатчит соответствующий экшен в store (например, DATA_RECEIVED). Reducer обновляет состояние, а селекторы с мемоизацией (через createSelector) фильтруют и агрегируют данные перед передачей в компоненты. Для обработки ошибок и переподключения используется exponential backoff с ограничением числа попыток. Важно избегать утечек: очищать таймеры и закрывать сокет при unmount приложения.

На практике

Типичный сценарий: биржевой тикер или чат. Middleware создаётся как функция с closure для хранения экземпляра WebSocket. При WS_CONNECT создаётся соединение, вешаются обработчики onmessage, onerror, onclose. В onmessage диспатчится экшен с данными. Для reconnect в onclose запускается setTimeout с увеличивающейся задержкой. Компонент-контейнер использует useEffect для вызова dispatch(wsConnect()) при монтировании и dispatch(wsDisconnect()) при размонтировании. Для отображения списка данных используется React.memo и виртуализация (react-window) при большом потоке. Для фильтрации по времени или типу применяется middleware-трансформация перед диспатчем.

Пример кода

JAVASCRIPT
// wsMiddleware.js
const wsMiddleware = () => {
let socket = null;
let reconnectTimer = null;
let reconnectAttempts = 0;
const connect = (dispatch, url) => {
socket = new WebSocket(url);
socket.onopen = () => {
reconnectAttempts = 0;
dispatch({ type: 'WS_CONNECTED' });
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
dispatch({ type: 'DATA_RECEIVED', payload: data });
};
socket.onclose = () => {
dispatch({ type: 'WS_DISCONNECTED' });
scheduleReconnect(dispatch, url);
};
socket.onerror = (error) => {
dispatch({ type: 'WS_ERROR', payload: error });
};
};
const scheduleReconnect = (dispatch, url) => {
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
reconnectTimer = setTimeout(() => {
reconnectAttempts++;
connect(dispatch, url);
}, delay);
};
return (store) => (next) => (action) => {
switch (action.type) {
case 'WS_CONNECT':
connect(store.dispatch, action.payload.url);
break;
case 'WS_DISCONNECT':
clearTimeout(reconnectTimer);
if (socket) socket.close();
break;
case 'WS_SEND':
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(action.payload));
}
break;
default:
return next(action);
}
};
};
// Компонент
function Ticker() {
const dispatch = useDispatch();
const prices = useSelector(state => state.prices);
const isConnected = useSelector(state => state.ws.connected);
useEffect(() => {
dispatch({ type: 'WS_CONNECT', payload: { url: 'wss://example.com/feed' } });
return () => dispatch({ type: 'WS_DISCONNECT' });
}, [dispatch]);
return (
<div>
<StatusIndicator connected={isConnected} />
<PriceList items={prices} />
</div>
);
}

Как отвечать на собеседовании

Начни с общей архитектуры: middleware как точка интеграции WebSocket с Redux. Объясни жизненный цикл соединения и обработку ошибок. Упомяни оптимизации: debounce для частых обновлений, мемоизированные селекторы, виртуализацию списка. Если спросят про альтернативы - сравни с RTK Query или React Query для real-time данных. Покажи понимание trade-off: middleware vs. саги (redux-saga) для сложной логики reconnect. Подчеркни важность очистки ресурсов и обработки race conditions при быстром unmount/remount.

Что проверяет интервьюер

  • Понимание потока данных: WebSocket → middleware → store → selector → component
  • Умение управлять побочными эффектами в Redux (middleware vs. thunk vs. saga)
  • Знание паттернов reconnect, exponential backoff, debounce
  • Способность предотвращать утечки памяти и race conditions
  • Опыт оптимизации производительности при высокочастотных обновлениях

Типичные ошибки

  • Создание WebSocket внутри компонента без middleware, что приводит к дублированию соединений при ререндере
  • Отсутствие очистки таймеров reconnect при unmount - утечка памяти
  • Диспатч каждого сообщения без debounce, вызывающий лавину ререндеров
  • Игнорирование обработки ошибок WebSocket (onerror, onclose без reconnect)
  • Хранение сырых данных в store без нормализации, что усложняет селекторы
  • Неправильное управление контекстом this в обработчиках событий WebSocket

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью