Можно ли использовать Redux без React в Node.js приложении

«Можно ли использовать Redux без React в Node.js приложении» — вопрос из категории JavaScript, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, Redux можно использовать без React, так как это независимая библиотека управления состоянием. В Node.js я применял Redux для:

1. Управления состоянием CLI-приложения:

const { createStore, combineReducers } = require('redux');
const reduxLogger = require('redux-logger');

// Редьюсеры
const configReducer = (state = { theme: 'dark', language: 'en' }, action) => {
  switch (action.type) {
    case 'SET_THEME':
      return { ...state, theme: action.payload };
    case 'SET_LANGUAGE':
      return { ...state, language: action.payload };
    default:
      return state;
  }
};

const tasksReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TASK':
      return [...state, { 
        id: Date.now(), 
        text: action.payload, 
        completed: false 
      }];
    case 'TOGGLE_TASK':
      return state.map(task =>
        task.id === action.payload
          ? { ...task, completed: !task.completed }
          : task
      );
    default:
      return state;
  }
};

// Комбинируем редьюсеры
const rootReducer = combineReducers({
  config: configReducer,
  tasks: tasksReducer,
});

// Создаем store с middleware
const store = createStore(
  rootReducer,
  applyMiddleware(reduxLogger.createLogger())
);

// Подписываемся на изменения
store.subscribe(() => {
  const state = store.getState();
  console.log('State changed:');
  console.log('- Config:', state.config);
  console.log('- Tasks:', state.tasks.length);
});

// Диспатчим действия
store.dispatch({ type: 'ADD_TASK', payload: 'Implement Redux in CLI' });
store.dispatch({ type: 'SET_THEME', payload: 'light' });

2. Обработки состояний воркеров (worker threads):

// worker-state-manager.js
const { createStore } = require('redux');
const { workerData, parentPort } = require('worker_threads');

// Редьюсер для состояния воркера
const workerReducer = (state = { 
  status: 'idle', 
  processed: 0, 
  errors: [] 
}, action) => {
  switch (action.type) {
    case 'PROCESS_START':
      return { ...state, status: 'processing' };
    case 'PROCESS_SUCCESS':
      return { 
        ...state, 
        status: 'idle', 
        processed: state.processed + 1 
      };
    case 'PROCESS_ERROR':
      return {
        ...state,
        errors: [...state.errors, action.payload],
        status: 'error'
      };
    case 'RESET_ERRORS':
      return { ...state, errors: [], status: 'idle' };
    default:
      return state;
  }
};

const store = createStore(workerReducer);

// Отправляем состояние в основной поток при изменениях
store.subscribe(() => {
  parentPort.postMessage({
    type: 'STATE_UPDATE',
    payload: store.getState()
  });
});

// Обработка команд из основного потока
parentPort.on('message', (action) => {
  store.dispatch(action);
});

3. Управления состоянием WebSocket сервера:

// websocket-state.js
const WebSocket = require('ws');
const { createStore } = require('redux');

// Редьюсер для подключений
const connectionsReducer = (state = { clients: new Map() }, action) => {
  switch (action.type) {
    case 'CLIENT_CONNECTED':
      state.clients.set(action.payload.clientId, {
        ws: action.payload.ws,
        connectedAt: Date.now(),
        lastActivity: Date.now(),
      });
      return { ...state, clients: new Map(state.clients) };

    case 'CLIENT_DISCONNECTED':
      state.clients.delete(action.payload);
      return { ...state, clients: new Map(state.clients) };

    case 'UPDATE_ACTIVITY':
      const client = state.clients.get(action.payload);
      if (client) {
        client.lastActivity = Date.now();
        state.clients.set(action.payload, client);
      }
      return { ...state, clients: new Map(state.clients) };

    default:
      return state;
  }
};

const store = createStore(connectionsReducer);

// Middleware для логирования
const logger = store => next => action => {
  console.log(`[${new Date().toISOString()}] Dispatching:`, action.type);
  const result = next(action);
  console.log('Next state:', store.getState().clients.size, 'clients');
  return result;
};

// Использование в WebSocket сервере
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  const clientId = generateId();

  store.dispatch({
    type: 'CLIENT_CONNECTED',
    payload: { clientId, ws }
  });

  ws.on('message', (message) => {
    store.dispatch({ type: 'UPDATE_ACTIVITY', payload: clientId });
    // Обработка сообщения
  });

  ws.on('close', () => {
    store.dispatch({ type: 'CLIENT_DISCONNECTED', payload: clientId });
  });
});

// Периодическая очистка неактивных клиентов
setInterval(() => {
  const state = store.getState();
  const now = Date.now();

  for (const [clientId, data] of state.clients) {
    if (now - data.lastActivity > 300000) { // 5 минут
      data.ws.terminate();
      store.dispatch({ type: 'CLIENT_DISCONNECTED', payload: clientId });
    }
  }
}, 60000);

Преимущества Redux в Node.js:

  • Предсказуемость — состояние изменяется только через actions
  • Отладка — можно логировать каждое изменение состояния
  • Тестируемость — редьюсеры это чистые функции
  • Time-travel debugging — с redux-devtools можно воспроизводить состояния

Когда стоит использовать:

  • Сложные CLI-инструменты с множеством состояний
  • Серверные приложения с управляемым состоянием (игры, чаты)
  • Воркеры, которым нужно синхронизировать состояние
  • Тестирование бизнес-логики независимо от UI