Hooks нужны не ради нового синтаксиса, а ради более простого способа работать с состоянием, побочными эффектами и повторным использованием логики. До них схожий код часто размазывался по классам, HOC и render props. С hooks тот же сценарий обычно читается короче и ближе к самой задаче.
Если воспринимать hooks как набор “магических функций”, ими легко злоупотреблять. Если же понимать, какую проблему решает каждый хук, то и useState, и useEffect, и useReducer становятся обычными инструментами, а не источником случайных оптимизаций.
- Как использовать useState Hook
- Как использовать useEffect Hook
- Правила Hooks
- ESLint Плагин
- Дополнительные Hooks
Обновлено 17 марта 2026: статья переписана под современный React, а устаревшие и сломанные примеры заменены на рабочие.
Как использовать useState Hook
useState добавляет локальное состояние в функциональный компонент. Раньше для этого часто приходилось писать класс:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
}С hooks тот же сценарий выглядит проще:
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount((current) => current + 1)}>
Increment
</button>
<button onClick={() => setCount((current) => current - 1)}>
Decrement
</button>
</div>
);
}useState возвращает пару: текущее значение и функцию обновления. Если новое состояние зависит от предыдущего, лучше сразу использовать функциональную форму setState, как в примере выше.
Как использовать useEffect Hook
useEffect нужен для побочных эффектов: сетевых запросов, подписок, таймеров, синхронизации с браузерным API и других действий, которые не должны происходить прямо во время рендера.
import { useEffect, useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState("Alex");
useEffect(() => {
document.title = `${name}: ${count}`;
}, [name, count]);
return (
<div>
<p>
Hi {name}, you clicked {count} times
</p>
<button onClick={() => setCount((current) => current + 1)}>
Increment
</button>
<button
onClick={() =>
setName((current) => (current === "Alex" ? "Alexey" : "Alex"))
}
>
Change name
</button>
</div>
);
}Массив зависимостей определяет, когда эффект нужно повторить. Если в массиве [name, count], эффект повторится только при изменении name или count.
Для очистки эффекта верни функцию:
useEffect(() => {
const id = window.setInterval(() => {
console.log("tick");
}, 1000);
return () => {
window.clearInterval(id);
};
}, []);Важно: useEffect не нужен для обычных вычислений. Если значение можно получить прямо во время рендера без побочного эффекта, хук здесь не нужен.
Правила Hooks
Hooks - обычные функции JavaScript, но React ожидает от них строгий порядок вызова:
- Используй Hooks только на верхнем уровне. Не вызывай hooks внутри циклов, условий или вложенных функций.
- Используй Hooks только из функций React. Не вызывай hooks из обычных утилит.
ESLint Плагин
Лучше не полагаться на память. Правила hooks должен проверять линтер.
npm install --save-dev eslint-plugin-react-hooks// eslint.config.js
const reactHooks = require("eslint-plugin-react-hooks");
module.exports = [
{
plugins: {
"react-hooks": reactHooks,
},
rules: {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
},
},
];Подробнее об ESLint
Дополнительные Hooks
Следующие hooks полезны уже в более конкретных сценариях.
useReducer
useReducer хорошо подходит, когда состояние связано несколькими переходами и useState начинает расползаться по компоненту.
import { useReducer } from "react";
const initialState = { count: 0, name: "Alex" };
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { ...state, count: state.count + 1 };
case "DECREMENT":
return { ...state, count: state.count - 1 };
case "CHANGE_NAME":
return {
...state,
name: state.name === "Alex" ? "Alexey" : "Alex",
};
default:
return state;
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>
Hi {state.name} you clicked {state.count} times
</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>
Increment
</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>
Decrement
</button>
<button onClick={() => dispatch({ type: "CHANGE_NAME" })}>
Change name
</button>
</div>
);
}Здесь вся логика переходов живет в одном месте, и это чаще удобнее, чем несколько разрозненных setState.
Пользовательский хук или Custom hook
Custom hook нужен для повторного использования логики, а не JSX. Название должно начинаться с use.
import { useEffect, useState } from "react";
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const savedValue = window.localStorage.getItem(key);
return savedValue !== null ? JSON.parse(savedValue) : initialValue;
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}Это хороший пример: состояние и синхронизация с внешним API браузера вынесены в переиспользуемую функцию.
Существующие хуки
Экосистема hooks давно выросла. Из полезных источников:
useMemo
useMemo возвращает запомненное значение и нужен для дорогих вычислений, а не “для красоты”.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);Не используй его для побочных эффектов и не пытайся через него хранить бизнес-логику. Если значение легко посчитать, useMemo чаще всего не нужен.
useCallback и React.memo
useCallback полезен, когда ссылка на функцию сама по себе важна, например для React.memo.
import { memo, useCallback, useReducer } from "react";
const Buttons = memo(function Buttons({ onIncrement, onDecrement }) {
return (
<div>
<button onClick={onIncrement}>Increment</button>
<button onClick={onDecrement}>Decrement</button>
</div>
);
});
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
const onIncrement = useCallback(() => {
dispatch({ type: "INCREMENT" });
}, []);
const onDecrement = useCallback(() => {
dispatch({ type: "DECREMENT" });
}, []);
return (
<>
<p>{state.count}</p>
<Buttons onIncrement={onIncrement} onDecrement={onDecrement} />
</>
);
}React.memo и useCallback не нужны по умолчанию, но хорошо работают вместе там, где действительно есть проблема лишних ререндеров.