Как использовать ванильный класс в React: компонент, хук или сервис?
При переходе с ванильного JavaScript на React часто возникает вопрос: как перенести существующий класс, который хранит состояние и вспомогательные методы для работы с DOM и историей, в новую экосистему. Рассмотрим основные подходы, их плюсы и минусы, чтобы вы могли выбрать оптимальное решение для вашего SPA.
Вариант 1: Класс как компонент верхнего уровня
Самый прямолинейный способ - сделать ваш класс React-компонентом, который будет оборачивать всё приложение. Внутри него можно использовать методы жизненного цикла (componentDidMount, componentWillUnmount) для работы с DOM и историей, а дочерние компоненты (children) будут получать необходимые данные через props или контекст.
Однако этот подход может привести к созданию «толстого» компонента, который сложно тестировать и поддерживать. Кроме того, классовые компоненты в React постепенно вытесняются функциональными с хуками, поэтому такой вариант считается устаревшим для новых проектов.
Вариант 2: Пользовательский хук (custom hook)
Более современный и гибкий способ - вынести логику вашего класса в пользовательский хук. Вы создаёте функцию, которая использует встроенные хуки (useState, useEffect, useRef) для управления состоянием и побочными эффектами. Затем этот хук можно вызывать в любом функциональном компоненте, получая доступ к методам и данным.
Например, если класс управлял историей переходов, вы можете создать хук useHistoryManager, который будет подписываться на изменения popstate и предоставлять методы навигации. Это делает код переиспользуемым и легко тестируемым.
Пример структуры хука
function useHistoryManager(initialState) {\n const [history, setHistory] = useState(initialState);\n useEffect(() => {\n const handlePopState = (event) => { /* обновление истории */ };\n window.addEventListener('popstate', handlePopState);\n return () => window.removeEventListener('popstate', handlePopState);\n }, []);\n const push = (state) => { /* ... */ };\n return { history, push };\n}Вариант 3: Отдельный сервис (singleton)
Если ваш класс не зависит от жизненного цикла React-компонентов и не должен перерисовывать UI при изменении состояния, его можно оставить как отдельный модуль-синглтон. В этом случае вы просто импортируете экземпляр класса в нужные компоненты и используете его методы напрямую. Однако такой подход нарушает принцип реактивности: изменения в сервисе не будут автоматически вызывать перерендер компонентов. Чтобы это исправить, придётся вручную подписываться на события или использовать библиотеки управления состоянием (например, MobX или Zustand).
Рекомендации для миграции
- Оцените зависимость от DOM: если класс напрямую манипулирует элементами (например, через document.getElementById), замените эти операции на React-рефы (useRef) или управляйте через состояние.
- Разделите ответственность: методы работы с историей, хранение данных и DOM-манипуляции лучше вынести в разные хуки или модули.
- Используйте Context API: если данные из класса нужны многим компонентам, создайте провайдер контекста, который будет передавать значения, полученные из хука.
В итоге, для большинства случаев оптимальным решением будет создание пользовательского хука. Это сохраняет реактивность, упрощает тестирование и соответствует современным практикам React-разработки. Если же класс реализует чисто инфраструктурную логику (например, логирование или аналитику), его можно оставить как отдельный сервис, но с осторожностью.