Как ответить
Обычно я реализую переиспользуемую логику в React через кастомные хуки. Это позволяет вынести побочные эффекты, управление состоянием и взаимодействие с API за пределы компонента, сохраняя модульность и упрощая тестирование. В отличие от HOC или render props, хуки дают более гибкую композицию без лишней вложенности.
Например, хук для работы с localStorage:
import { useState, useEffect } from 'react';
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue] as const;
}Хук синхронизирует состояние с localStorage, обрабатывает ошибки и поддерживает ленивую инициализацию. Используя его, компонент остаётся чистым, а логика хранения — изолированной. Подобные хуки я тестирую с помощью @testing-library/react-hooks, отдельно проверяя инициализацию, запись и синхронизацию между вкладками.
Другой пример — хук useIntersectionObserver для ленивой загрузки. Он создаёт observer в useEffect и возвращает ref-объект, который вешается на DOM-элемент. Такие хуки можно компоновать: один хук подписывается на скролл, другой — на изменение размеров, третий — на загрузку данных.
Главное — соблюдать правила хуков: не вызывать их внутри условий или циклов. Если нужно условное применение, лучше разбить на несколько хуков или вынести в дочерний компонент. Для сложной логики с асинхронными запросами я добавляю useReducer, чтобы обрабатывать состояния загрузки, ошибки и данных.