Почему появился React Hooks
До версии 16.8 React требовал классовых компонентов для работы со состоянием и жизненным циклом. Это приводило к громоздким паттернам — Higher‑Order Components, render‑props, “wrapper hell”. Код становился трудно поддерживаемым, а повторное использование логики — почти невозможным. Hooks изменили подход: теперь состояние и побочные эффекты инкапсулируются в обычные функции, которые можно подключать к любому функциональному компоненту. Это упростило структуру приложений, сделало их более предсказуемыми и улучшило читаемость кода.
useState: больше, чем просто состояние
useState предоставляет простой способ объявить локальное состояние внутри функции‑компонента. При этом каждый вызов возвращает пару — текущее значение и функцию‑сеттер.
const Counter = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Нажали {count} раз
</button>
);
};
Ключевые нюансы:
- Функция‑сеттер принимает как новое значение, так и функцию‑обновления, что гарантирует актуальное состояние при быстрых последовательных вызовах.
- При работе с объектами или массивами важно использовать иммутабельные обновления, иначе React может не увидеть изменение.
- Инициализатор может быть функцией — это полезно, когда начальное значение требует тяжёлых вычислений.
useEffect: правильное использование
useEffect заменяет методы жизненного цикла componentDidMount, componentDidUpdate и componentWillUnmount. Его сигнатура принимает функцию‑эффект и массив зависимостей.
useEffect(() => {
const id = setInterval(fetchData, 5000);
return () => clearInterval(id); // очистка при размонтировании
}, [userId]); // эффект перезапускается только при изменении userId
Главные рекомендации:
- Всегда указывайте зависимости; отсутствие массива приводит к выполнению эффекта после каждого рендера.
- При необходимости выполнить эффект один раз после монтирования передайте пустой массив
[]. - Не вызывайте асинхронные функции напрямую в теле
useEffect; используйте внутреннюю async‑функцию или промисы.
useRef: не только ссылки на DOM
useRef создаёт мутируемый объект { current: … }, который сохраняет значение между рендерами без триггера повторного рендера.
- DOM‑рефы:
ref={myRef}в JSX позволяет получить доступ к элементу. - Хранение предыдущих значений: удобно для сравнения текущего и предыдущего пропов без лишних вычислений.
- Таймеры и идентификаторы: можно сохранять
setTimeout‑идентификаторы и очищать их вuseEffect.
const prevCount = useRef();
useEffect(() => {
prevCount.current = count;
});
useContext: совместное состояние без Redux
Контекст — механизм глобального доступа к данным. useContext упрощает потребление контекста в функциях‑компонентах.
const ThemeContext = React.createContext('light');
const Toolbar = () => {
const theme = useContext(ThemeContext);
return <div className={theme}>Панель</div>;
};
Плюсы использования контекста:
- Избавление от проп‑прокидывания на несколько уровней.
- Возможность вынести настройки темы, локализацию, пользовательские данные в один источник.
- При правильном разделении контекстов (мелкие, специализированные) можно избежать лишних перерисовок.
useReducer: управление сложным состоянием
Для состояний, где логика обновления сложна, useReducer предоставляет паттерн, аналогичный Redux, но без внешней библиотеки.
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case 'inc':
return { ...state, count: state.count + state.step };
case 'setStep':
return { ...state, step: action.payload };
default:
throw new Error('Неизвестное действие');
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
// …
};
Преимущества:
- Чёткая декларация переходов состояния.
- Возможность вынести reducer в отдельный файл и переиспользовать в разных компонентах.
- Хорошая совместимость с
useContextдля создания собственного "mini‑store".
useMemo и useCallback: когда действительно нужны
Эти хуки предназначены для оптимизации, а не для преждевременного кеширования.
useMemoкэширует результат вычисления, возвращая его только при изменении зависимостей.useCallbackкэширует саму функцию, что полезно при передаче её в дочерние компоненты, зависящие отReact.memo.
const sorted = useMemo(() => items.sort(compare), [items]);
const handleClick = useCallback(() => doSomething(id), [id]);
Важно помнить, что избыточное использование приводит к дополнительным вычислениям и усложнённому коду. Применяйте их только там, где измеримая выгода (например, дорогие сортировки или частые ререндеры дочерних компонентов).
Создание собственных хуков: реальные возможности
Кастомные хуки позволяют вынести повторяющуюся бизнес‑логику в переиспользуемый модуль. Они следуют тем же правилам, что и встроенные: вызываются только на верхнем уровне функции‑компонента и могут использовать любые другие хуки.
Пример: хук для асинхронного запроса
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
setLoading(true);
fetch(url)
.then(res => res.json())
.then(json => {
if (isMounted) {
setData(json);
setError(null);
}
})
.catch(err => {
if (isMounted) setError(err);
})
.finally(() => {
if (isMounted) setLoading(false);
});
return () => {
isMounted = false;
};
}, [url]);
return { data, loading, error };
}
Вызов в компоненте:
const { data, loading, error } = useFetch('/api/users');
Принципы построения кастомных хуков
- Ясное название: начинайте с
use, напримерuseLocalStorage,useDebounce. - Минимальная зависимость: включайте только те хуки и параметры, которые действительно нужны.
- Возврат типизированных структур: массивы, объекты или кортежи, чтобы потребитель понимал, что именно получит.
- Тестируемость: вынесенную логику легко покрыть unit‑тестами, используя библиотеки вроде
@testing-library/react-hooks.
Чеклист лучших практик
- Следуйте правилу “вызывайте хуки только на верхнем уровне”. Не помещайте их в условные блоки, циклы или вложенные функции.
- Разделяйте состояние и эффекты. Храните данные в
useState/useReducer, а побочные действия — вuseEffect. - Минимизируйте количество зависимостей в
useEffect. Если функция‑обработчик меняется каждый рендер, вынесите её вuseCallback. - Не переусердствуйте с
useMemo/useCallback. Применяйте только при реальных проблемах производительности. - Избегайте прямой мутации объектов, хранимых в состоянии. Используйте иммутабельные операции или библиотеки вроде
immer. - Документируйте кастомные хуки. Указывайте типы входных параметров и возвращаемых значений, а также ожидаемое поведение.
- Тестируйте хуки отдельно от UI. Это упрощает поддерживаемость и позволяет быстро обнаружить регрессии.
- Объединяйте контекст и
useReducerдля глобального состояния. Это даёт лёгкую альтернативу Redux без лишних зависимостей.
Эти принципы помогут построить масштабируемую, чистую и легко поддерживаемую кодовую базу на основе современных возможностей React.