Hooki React — to funkcje, które umożliwiają używanie stanu, efektów, kontekstu i innych możliwości React w komponentach funkcyjnych, bez pisania klas. Wprowadzone w React 16.8, hooki radykalnie zmieniły podejście do programowania, zastępując złożone wzorce, takie jak HOC i render props. Według React, 2024, istnieje 15 wbudowanych hooków, pokrywających wszystkie scenariusze zarządzania komponentem.
Najważniejsze
Hooki React — zestaw funkcji wbudowanych w React, które umożliwiają „podłączanie się” do wewnętrznych mechanizmów frameworka bez tworzenia klas. Przed React 16.8 komponenty funkcyjne mogły tylko przyjmować props i zwracać JSX — każda złożona logika wymagała komponentu klasowego.
Problem komponentów klasowych — rozdzielenie powiązanej logiki. Kod odnoszący się do jednej jednostki (na przykład subskrypcja WebSocket) był wymuszenie podzielony między componentDidMount, componentDidUpdate i componentWillUnmount. Hooki rozwiązują to: cała logika grupuje się w jednym useEffect.
Drugi problem — ponowne użycie logiki. Do wielokrotnego użycia logiki stanu między komponentami klasowymi trzeba było stosować HOC (komponenty wyższego rzędu) lub render props — oba wzorce tworzyły dodatkowe zagnieżdżenie w drzewie komponentów. Własne hooki rozwiązują ten problem bez zagnieżdżania.
useState — najbardziej podstawowy hook. Przyjmuje wartość początkową i zwraca krotkę: bieżącą wartość i funkcję do jej aktualizacji. W przeciwieństwie do this.setState w klasach, useState nie scala obiektów — nowy stan całkowicie zastępuje poprzedni dla konkretnego wywołania useState.
import React, { useState } from 'react';
function Form() {
const [text, setText] = useState('');
const [submitting, setSubmitting] = useState(false);
async function handleSubmit() {
setSubmitting(true);
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify({ text })
});
setSubmitting(false);
}
return (
<div>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={handleSubmit}
disabled={submitting}>
{submitting ? 'Saving...' : 'Submit'}
</button>
</div>
);
}
Setter useState może przyjmować funkcję, która otrzymuje poprzednią wartość i zwraca nową. Funkcyjna forma jest obowiązkowa, gdy nowy stan jest obliczany z poprzedniego i możliwe są batczowane wywołania setState — każde wywołanie otrzyma aktualną poprzednią wartość.
useEffect — hook do wykonywania efektów ubocznych w komponentach funkcyjnych. Przyjmuje dwa argumenty: funkcję-efekt i tablicę zależności. React wykonuje efekt po każdym renderze, ale przy określeniu zależności — tylko gdy którakolwiek z nich się zmieniła.
Pusta tablica zależności [] oznacza, że efekt wykonuje się raz po pierwszym renderze (odpowiednik componentDidMount). Zwrócenie funkcji z efektu to cleanup, który wykonuje się przy odmontowywaniu (odpowiednik componentWillUnmount) i przed ponownym wykonaniem efektu.
import React, { useState, useEffect } from 'react';
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket(`wss://chat/${roomId}`);
socket.onmessage = (event) => {
setMessages(prev => [...prev, JSON.parse(event.data)]);
};
// Cleanup - zamknij gniazdo
return () => socket.close();
}, [roomId]);
return <ul>
{messages.map((msg, i) =>
<li key={i}>{msg.text}</li>
)}
</ul>;
}
Zależności useEffect — to jedyne połączenie między efektem a światem zewnętrznym. Jeśli w efekcie używana jest zmienna z propsów lub stanu, musi być w tablicy zależności. Ignorowanie tej zasady jest źródłem błędów z nieaktualnymi danymi (stale closure).
useContext — hook do odczytywania wartości kontekstu. Przyjmuje obiekt kontekstu utworzony przez React.createContext i zwraca bieżącą wartość. Jeśli kontekst się zmienia, komponent automatycznie przerenderowuje się z nową wartością.
Przed pojawieniem się useContext dostęp do kontekstu w komponentach funkcyjnych wymagał opakowania w Context.Consumer, co tworzyło zagnieżdżenie. useContext całkowicie eliminuje Consumer — wystarczy wywołać hook w dowolnym miejscu komponentu funkcyjnego.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('jasny');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button
className={`btn btn-${theme}`}
>
{theme === 'dark' ? 'moon' : '☀️'}
</button>;
}
useContext nie jest zamiennikiem Redux ani globalnego stanu. React zaleca używanie kontekstu dla rzadko zmieniających się wartości — motyw, język, autoryzacja. Dla często aktualizowanych danych każdy re-render będzie ponownie obliczał wszystkich konsumentów kontekstu, co może obniżyć wydajność.
useReducer — alternatywa dla useState dla stanów ze złożoną logiką aktualizacji. Przyjmuje funkcję reducer i stan początkowy, zwraca bieżący stan i funkcję dispatch. Reducer otrzymuje bieżący stan i akcję, zwraca nowy stan — czysta funkcja bez efektów ubocznych.
useReducer jest szczególnie przydatny, gdy kilka akcji (set, reset, increment, addItem) zarządza jednym stanem. Zamiast wielu useState z różnymi setterami — jeden reducer z wyraźnymi typami akcji. To czyni logikę przewidywalną i łatwą do testowania.
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'setStep':
return { ...state, step: action.step };
case 'reset':
return { count: 0, step: 1 };
default:
throw new Error('Unknown action: ' + action.type);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, {
count: 0,
step: 1
});
return (
<>
<p>Count: {state.count}</p>
<button
onClick={() => dispatch({ type: 'increment' })}>+</button>
<button
onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</>
);
}
useRef tworzy zmienialny obiekt, który utrzymuje się między renderami. Zmiana current nie powoduje ponownego renderowania. Główne zastosowanie — dostęp do elementów DOM i przechowywanie wartości mutowalnych (timery, poprzednie wartości, instancje bibliotek).
useMemo memoizuje wynik funkcji. Przelicza wartość tylko gdy zmienia się jedna z zależności. Używaj do kosztownych obliczeń — filtrowanie dużych tablic, sortowanie, formatowanie danych. useCallback — szczególny przypadek useMemo do memoizacji funkcji.
import React, { useRef, useMemo, useCallback } from 'react';
function SearchPage({ items, query, onItemSelect }) {
const inputRef = useRef(null);
// Autofokus przy montowaniu
useEffect(() => {
inputRef.current?.focus();
}, []);
// Zapamiętaj przefiltrowany wynik
const filtered = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase()
.includes(query.toLowerCase())
);
}, [items, query]);
// Zapamiętaj callback
const handleSelect = useCallback((id) => {
onItemSelect(id);
}, [onItemSelect]);
return (
<div>
<input ref={inputRef}
value={query}
onChange={e => setQuery(e.target.value)}
/>
<ul>
{filtered.map(item =>
<li key={item.id}>
<button
onClick={() => handleSelect(item.id)}>
{item.name}
</button>
</li>
)}
</ul>
</div>
);
}
Ważna zasada: useRef, useMemo i useCallback — narzędzia optymalizacji, a nie obowiązkowe konstrukcje. Nie opakowuj wszystkiego w useMemo — najpierw zmierz wydajność. Przedwczesna optymalizacja komplikuje kod. React renderuje komponenty szybko; useMemo jest potrzebny tylko gdy obliczenia są naprawdę kosztowne (O(n²) i wyżej).
React wprowadził dwie sztywne zasady dla hooków. Pierwsza: wywołuj hooki tylko na najwyższym poziomie — nie wewnątrz warunków, pętli ani zagnieżdżonych funkcji. Druga: wywołuj hooki tylko z komponentów funkcyjnych React lub własnych hooków. ESLint-plugin eslint-plugin-react-hooks automatycznie sprawdza obie zasady.
Najczęstszy błąd — naruszenie kolejności wywoływania hooków. React polega na kolejności wywołania hooków między renderami. Jeśli hook znajduje się wewnątrz warunku i nie został wywołany w jednym z renderów, wszystkie następne hooki przesuwają się, powodując błędy.
| Błąd | Co się dzieje | Rozwiązanie |
|---|---|---|
| Hook w warunku | Kolejność hooków się łamie | Przenieś logikę warunku do wewnątrz hooka |
| Stale closure | Efekt używa starej wartości | Dodaj wszystkie zależności do tablicy |
| Nieskończona pętla | useEffect bez zależności zmienia state | Określ zależności lub użyj useReducer |
| Modyfikacja ref w render | Efekt uboczny w ciele funkcji | Przenieś mutacje do useEffect |
Drugi częsty błąd — stale closure w useEffect. Jeśli w efekcie używana jest zmienna nieokreślona w zależnościach, efekt „chwyta” jej wartość w momencie tworzenia domknięcia. Rozwiązanie — zawsze uwzględniaj wszystkie używane zmienne w tablicy zależności. Jeśli zależności jest zbyt wiele — można podzielić efekt na kilka.
Często zadawane pytania
React polega na kolejności wywołania hooków między renderami. Jeśli hook został wywołany w warunku i nie zadziałał w jednym z renderów, wszystkie następne hooki „przesuwają się” — ich stan się miesza. Reguła ESLint hooks/exhaustive-deps ostrzega przed tym.
useLayoutEffect jest synchroniczny i wykonuje się przed tym, jak przeglądarka narysuje zmiany. useEffect jest asynchroniczny — po narysowaniu. Używaj useLayoutEffect tylko gdy trzeba zmierzyć lub zmienić DOM zanim użytkownik zobaczy zmiany (pozycja popover, scroll restoration).
Użyj AbortController wewnątrz useEffect. Utwórz controller, przekaż signal do fetch, a w funkcji cleanup wywołaj controller.abort(). Dla starszych przeglądarek wystarczy flaga cancelled: return () => { cancelled = true; } z sprawdzeniem przed setState.
Własny hook — funkcja JavaScript z prefiksem use, używająca wbudowanych hooków. Twórz go, gdy ta sama logika z hookami powtarza się w dwóch lub więcej komponentach. Przykłady: useDebounce, useLocalStorage, useMediaQuery. Własne hooki zastępują HOC i render props.
W Strict Mode React celowo montuje i odmontowuje komponent dwukrotnie w trybie deweloperskim, aby znaleźć błędy z nieoczyszczonymi efektami. Jeśli cleanup jest napisany poprawnie, podwójne wywołanie nie powoduje problemów. W trybie produkcyjnym to nie występuje.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również