Перейти к содержимому

Введение в React Hooks

Hooks появились в React 16.8 и позволяют использовать состояние и другие возможности React в функциональных компонентах, без необходимости писать классы.

До Hooks логика состояния была привязана к классовым компонентам, что делало переиспользование логики сложным (HOC, render props). Hooks решают эту проблему — логику можно вынести в кастомный хук и использовать в любом компоненте.

Правило 1: Вызывай только на верхнем уровне

Заголовок раздела «Правило 1: Вызывай только на верхнем уровне»
// ❌ Нельзя — Hook внутри условия
function Component({ show }: { show: boolean }) {
if (show) {
const [count, setCount] = useState(0); // ОШИБКА
}
}
// ✅ Можно — Hook на верхнем уровне
function Component({ show }: { show: boolean }) {
const [count, setCount] = useState(0);
if (!show) return null;
return <div>{count}</div>;
}
// ❌ Нельзя — Hook в обычной функции
function fetchData() {
const [data, setData] = useState(null); // ОШИБКА
}
// ✅ Можно — только в компонентах и кастомных хуках
function MyComponent() {
const [data, setData] = useState(null);
return <div>{data}</div>;
}
// ✅ Можно — в кастомных хуках (начинаются с "use")
function useFetch(url: string) {
const [data, setData] = useState(null);
// ...
return data;
}

До React 18 setState в обработчиках событий батчился, но не в асинхронных операциях:

// React 17: 2 рендера
setTimeout(() => {
setCount(c => c + 1); // рендер 1
setFlag(f => !f); // рендер 2
}, 1000);
// React 18: 1 рендер (автоматический batching)
setTimeout(() => {
setCount(c => c + 1); // }
setFlag(f => !f); // } один рендер
}, 1000);

Если нужно отключить batching:

import { flushSync } from 'react-dom';
flushSync(() => setCount(c => c + 1)); // немедленный рендер
flushSync(() => setFlag(f => !f)); // ещё один рендер

Позволяет помечать обновления как “не срочные”:

import { useState, useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<string[]>([]);
const [isPending, startTransition] = useTransition();
function handleChange(value: string) {
setQuery(value); // срочное: обновить поле ввода
startTransition(() => {
// не срочное: можно прерывать
setResults(expensiveSearch(value));
});
}
return (
<>
<input value={query} onChange={e => handleChange(e.target.value)} />
{isPending ? <span>Загрузка...</span> : <List items={results} />}
</>
);
}

Альтернатива useTransition когда нельзя контролировать источник обновления:

import { useDeferredValue } from 'react';
function SearchResults({ query }: { query: string }) {
const deferredQuery = useDeferredValue(query);
// deferredQuery отстаёт от query — React перерисует с новым значением,
// когда будет свободен
const results = expensiveSearch(deferredQuery);
return <List items={results} />;
}
ХукНазначение
useStateЛокальное состояние
useEffectПобочные эффекты
useContextЧтение контекста
useReducerСложное состояние
useCallbackМемоизация функций
useMemoМемоизация значений
useRefСсылки / мутабельные значения
useIdУникальные ID (React 18)
useTransitionНе срочные обновления (React 18)
useDeferredValueОткладывание обновления (React 18)
useSyncExternalStoreВнешние хранилища (React 18)
function Example() {
console.log('1. render');
useEffect(() => {
console.log('3. after paint (effect)');
return () => console.log('cleanup');
});
useLayoutEffect(() => {
console.log('2. after DOM mutation (layout effect)');
});
return <div />;
}
// Порядок: 1 → 2 → paint → 3