Zasady hooków są dwie: wywołuj hooki tylko na najwyższym poziomie komponentu, nigdy w warunku, pętli, zagnieżdżonej funkcji ani po wczesnym return; i wywołuj je tylko z komponentów funkcyjnych albo z własnych hooków. Istnieją, bo React odróżnia hooki po kolejności, w jakiej je wywołujesz, więc ta kolejność musi być taka sama przy każdym renderowaniu.
React przechowuje wartości hooków komponentu na liście przypisanej do tego komponentu. Przy każdym renderowaniu przechodzi po tej liście: pierwsze wywołanie hooka dostaje pierwsze miejsce, drugie wywołanie drugie miejsce i tak dalej. Nie ma nazw, są tylko pozycje. Przykład poniżej to zabawkowa wersja tego pomysłu w kilku liniach zwykłego JavaScriptu, a nie własny kod Reacta, więc możesz go bezpiecznie psuć.
Pierwsze renderowanie wypełnia trzy miejsca: Ada, Countess, 36. Kliknij "Render without nickname": wywołanie warunkowe zostaje pominięte, więc wywołanie age zajmuje miejsce 1 i odczytuje Countess. Prawdziwy React w niektórych przypadkach zauważa niezgodność i rzuca błąd, a w innych oddaje złą wartość złemu hookowi, co jest gorsze. Przenieś wywołanie toyUseState('Countess') nad if, a oba przyciski pokażą właściwy wiek.
Dwie zasady w kodzie
function Profile({ user }) {
// Good: always called, always in this order
const [tab, setTab] = useState('posts');
const theme = useContext(ThemeContext);
useEffect(() => {
document.title = user.name;
}, [user.name]);
// Bad: inside a condition, a loop or a nested function
if (user.isAdmin) {
const [log, setLog] = useState([]); // breaks the order
}
}
Wywołanie hooka, do którego zawsze dochodzi, w tej samej kolejności, jest w porządku bez względu na to, gdzie stoi w ciele komponentu, pod warunkiem że jest przed każdym wczesnym return.
Naprawa hooka wywołanego warunkowo
Linter zgłasza to jako React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Rozwiązanie jest zawsze takie samo: wywołuj hook za każdym razem, a warunek umieść w jego środku.
// Broken: the effect exists on some renders and not others
function Notifications({ user }) {
if (user) {
useEffect(() => {
console.log('subscribe for', user.name);
}, [user]);
}
return null;
}
Efekt jest deklarowany przy każdym renderowaniu, a if w jego środku decyduje, czy cokolwiek zrobić. Zaloguj się i wyloguj kilka razy, a konsola pokaże subskrypcję przy każdym logowaniu i rezygnację z subskrypcji przy każdym wylogowaniu.
Hooki po wczesnym return
Wczesny return to też warunek. Ten kod daje "Rendered more hooks than during the previous render" za pierwszym razem, gdy loading zmieni się na false, bo to renderowanie dochodzi do hooka, którego poprzednie nigdy nie wywołało. Gdy renderowanie wywołuje mniej hooków niż poprzednie, komunikat brzmi "Rendered fewer hooks than expected".
// Broken: useState is skipped while loading
function Comments({ loading, comments }) {
if (loading) return <p>Loading...</p>;
const [expanded, setExpanded] = useState(false);
return <List items={comments} expanded={expanded} />;
}
Każdy hook stoi nad return, więc obie gałęzie wywołują te same hooki. Kliknij "Toggle loading", rozwiń listę, a potem dwa razy przełącz ładowanie: lista pamięta, że była rozwinięta, bo stan nigdy nie został pominięty.
Hooki w pętli
Nie możesz wywołać useState raz na każdy element w pętli, bo liczba elementów się zmienia. Daj każdemu elementowi własny komponent i tam wywołaj hook; każda instancja ma własne miejsca.
„Invalid hook call"
Komunikat zaczyna się od "Invalid hook call. Hooks can only be called inside of the body of a function component." Ma trzy typowe przyczyny:
- Hook wykonuje się, gdy React nie renderuje komponentu funkcyjnego. W handlerze zdarzenia, wewnątrz callbacku efektu, w komponencie klasowym albo na poziomie modułu. Przenieś go do ciała komponentu albo własnego hooka i wywołaj własny hook z komponentu.
- Wersje
reactireact-domsię nie zgadzają. Zainstaluj tę samą wersję obu. - Są dwie kopie Reacta. Podlinkowany lokalny pakiet albo biblioteka, która dołącza własnego Reacta, wnosi drugą kopię, a hooki z jednej kopii nie działają z rendererem drugiej. Uruchom
npm ls reacti upewnij się, że pojawia się tylko jedna wersja.
npm ls react
Podgląd tutaj działa jak build produkcyjny, który nie wypisuje tych komunikatów: hook wywołany z handlera zdarzenia kończy się zwykłym TypeError, na przykład "Cannot read properties of null", a własne błędy Reacta przychodzą jako krótkie kody "Minified React error". Uruchom aplikację w trybie deweloperskim, żeby przeczytać pełny tekst.
Niech pilnuje tego linter
eslint-plugin-react-hooks sprawdza obie zasady w trakcie pisania, a do tego tablice zależności efektów. Zawierają go szablon React w Vite i konfiguracja ESLint w Next.js. W płaskiej konfiguracji ESLint włącz jego zalecany zestaw:
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([reactHooks.configs.flat.recommended]);
Dwie podstawowe reguły to react-hooks/rules-of-hooks (błąd) i react-hooks/exhaustive-deps (ostrzeżenie). Od wersji 7 zestaw włącza też reguły pochodzące z React Compiler, takie jak react-hooks/purity i react-hooks/refs, które zgłaszają nieczyste renderowanie i odczyt refów podczas renderowania. Żeby wybierać reguły pojedynczo, zarejestruj wtyczkę jako plugins: { 'react-hooks': reactHooks } i wymień reguły, których chcesz.
Wtyczka rozpoznaje hooki po nazwie: każda funkcja, której nazwa zaczyna się od use, a po nim wielkiej litery, jest traktowana jako hook i mogą ją wywoływać tylko komponenty (nazwy z wielkiej litery) i inne funkcje use. Dlatego własne hooki muszą używać tego przedrostka.
Wyjątek: use
use, dodany w React 19, odczytuje obietnicę albo kontekst i w odróżnieniu od każdego innego hooka może być wywołany w warunku albo pętli. Nadal musi być wywoływany podczas renderowania, wewnątrz komponentu albo hooka.
Gdy zmieniasz motyw, kolor zmienia tylko nagłówek z motywem. Spróbuj tego samego z useContext wewnątrz if, a linter to zgłosi. Strona o hooku use omawia odczytywanie nim obietnic.
Najczęściej zadawane pytania
Jakie są zasady hooków?
Są dwie. Wywołuj hooki tylko na najwyższym poziomie komponentu funkcyjnego albo własnego hooka, nigdy w warunkach, pętlach, zagnieżdżonych funkcjach ani po wczesnym return. I wywołuj hooki tylko z komponentów funkcyjnych albo własnych hooków, a nie ze zwykłych funkcji czy komponentów klasowych.
Dlaczego hooków nie można wywoływać warunkowo?
React nie zna twoich hooków po nazwie. Przechowuje ich wartości na liście i oddaje je w kolejności, w jakiej następują wywołania. Jeśli warunek pominie wywołanie, każdy późniejszy hook odczyta wartość, która należała do hooka przed nim.
Co znaczy "Rendered more hooks than during the previous render"?
Renderowanie wywołało więcej hooków niż poprzednie, zwykle dlatego, że hook siedzi w if albo po wczesnym return, które tym razem zadziałały inaczej. Przenieś każdy hook nad warunek, a warunek umieść wewnątrz hooka.
Jak naprawić "Invalid hook call"?
Sprawdź trzy rzeczy: hook wykonuje się, gdy React renderuje komponent funkcyjny (a nie w handlerze zdarzenia, callbacku efektu, komponencie klasowym ani na poziomie modułu), react i react-dom mają zgodne wersje, a aplikacja ma tylko jedną kopię Reacta (npm ls react je wypisuje).
Czy hook use można wywołać warunkowo?
Tak. use (React 19) to jedyny wyjątek: możesz go wywołać w blokach if i pętlach. Nadal trzeba go wywoływać wewnątrz komponentu albo hooka, a nie w handlerze zdarzenia.