Jak używać React Hooks: Kompletny przewodnik z przykładami
Jak używać React Hooks: Kompletny przewodnik z przykładami
Gdy na początku 2019 roku w React 16.8 pojawiły się Hooks, radykalnie zmieniło to podejście programistów do tworzenia aplikacji React, umożliwiając komponentom funkcyjnym zarządzanie stanem, efektami ubocznymi i kontekstem bez pisania komponentów klasowych. Zrozumienie jak efektywnie używać React Hooks jest teraz niezbędne dla każdego nowoczesnego programisty React, ponieważ Hooks stały się „jedynym sposobem pisania większości kodu React”. Ten przewodnik zapozna Cię z podstawowymi wbudowanymi Hookami, wyjaśni zasady ich używania i dostarczy praktycznych przykładów, które możesz zastosować od razu.
Czego się nauczysz
Pod koniec tego przewodnika będziesz pewnie używać podstawowych Hooków React — useState, useEffect, useContext, useRef, useReducer, useMemo i useCallback — w swoich projektach. Zrozumiesz, kiedy wybrać dany Hook, jak unikać typowych błędów, takich jak niepotrzebne przerenderowania, oraz jak wyodrębniać wielokrotnie używaną logikę do własnych Hooków. Najważniejszy wniosek: Hooks to po prostu funkcje; umiejętność ich łączenia jest kluczem do opanowania React.
Zrozumienie zasad używania Hooków
Zanim zagłębisz się w poszczególne Hooks, musisz przyswoić dwie niezmienne zasady, których przestrzeganie jest egzekwowane przez pakiet eslint-plugin-react-hooks:
Wywołuj Hooks tylko na najwyższym poziomie. Nigdy nie wywołuj Hooków wewnątrz pętli, instrukcji warunkowych lub zagnieżdżonych funkcji. React polega na kolejności wywołań Hooków, aby zachować stan między renderowaniami — jeśli kolejność się zmieni, React nie będzie w stanie prawidłowo dopasować stanu do jego Hooka.
Wywołuj Hooks tylko z funkcji React. Możesz wywoływać Hooks tylko z komponentów funkcyjnych lub z innych własnych Hooków. Zwykła funkcja JavaScript nie może wywoływać
useStateaniuseEffect.Google AdInline article slot
Z drugiej zasady wynika konwencja: każda funkcja wywołująca Hooks powinna zaczynać się od use (np. useCustomHook). Linter używa tego prefiksu do prawidłowego stosowania zasad.
Najważniejsze Hooks, których będziesz używać codziennie
useState: Zarządzanie stanem komponentu
Hook useState jest najbardziej podstawowy — umożliwia dodawanie stanu do komponentów funkcyjnych. Zwraca tablicę z dwóch elementów: bieżącą wartość stanu i funkcję do jej aktualizacji.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // Stan początkowy — 0
return (
<button onClick={() => setCount(count + 1)}>
Kliknięto {count} razy
</button>
);
}
Podczas aktualizacji stanu na podstawie poprzedniej wartości używaj funkcyjnej formy aktualizacji, aby uniknąć stanów wyścigu, szczególnie przy automatycznym grupowaniu React:
// Bezpiecznie: używa poprzedniej wartości stanu
setCount(prevCount => prevCount + 1);
// Również bezpiecznie dla obiektów: rozprzestrzenia poprzedni stan
setUser(prevUser => ({ ...prevUser, name: 'Jane' }));
Jeśli obliczanie stanu początkowego jest kosztowne, przekaż funkcję zamiast wartości — React wykona ją tylko przy początkowym renderowaniu:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
useEffect: Obsługa efektów ubocznych
Hook useEffect umożliwia wykonywanie efektów ubocznych w komponentach funkcyjnych, takich jak pobieranie danych, konfigurowanie subskrypcji lub ręczna modyfikacja DOM. Domyślnie efekty uruchamiają się po każdym zakończonym renderowaniu, ale możesz kontrolować, kiedy są wyzwalane, za pomocą tablicy zależności.
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// Ten efekt uruchamia się po zamontowaniu komponentu i przy zmianie userId
fetchUser(userId).then(data => setUser(data));
// Opcjonalna funkcja czyszcząca uruchamia się przed następnym efektem i przy odmontowaniu
return () => {
// Anulowanie oczekujących zapytań lub czyszczenie subskrypcji
};
}, [userId]); // Tablica zależności: ponowne uruchomienie tylko przy zmianie userId
return <div>{user?.name}</div>;
}
Kluczowe wzorce dla useEffect:
- Pusta tablica zależności
[]: Efekt uruchamia się raz po początkowym renderowaniu (zamontowaniu). - Bez tablicy zależności: Efekt uruchamia się po każdym renderowaniu (rzadko potrzebne).
- Funkcja czyszcząca: Zapobiega wyciekom pamięci, czyszcząc subskrypcje, timery lub oczekujące zapytania sieciowe.
Ważne ostrzeżenie: Nie każda zmiana stanu wymaga efektu. Jeśli możesz obliczyć wartość podczas renderowania, zrób to bezpośrednio, a nie przez useEffect:
// ❌ Niepotrzebny efekt
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
// ✅ Obliczenie bezpośrednio podczas renderowania
const fullName = `${firstName} ${lastName}`;
useContext: Korzystanie z kontekstu bez przeciągania propsów
Hook useContext umożliwia odczytywanie wartości z React Context bez owijania komponentu w Consumer:
import { useContext } from 'react';
// Tworzenie kontekstu
const ThemeContext = React.createContext('light');
function ThemedButton() {
// Bezpośrednie odczytanie wartości kontekstu
const theme = useContext(ThemeContext);
return <button className={theme}>Kliknij mnie</button>;
}
Gdy wartość providera kontekstu się zmieni, każdy komponent używający useContext automatycznie się przerenderowuje.
useRef: Referencje do wartości, które nie powodują przerenderowania
Hook useRef tworzy mutowalny obiekt ref, którego właściwość .current jest zachowywana między renderowaniami. W przeciwieństwie do stanu, aktualizacja ref nie powoduje przerenderowania. To sprawia, że refs są idealne do:
- Przechowywania referencji do elementów DOM
- Śledzenia identyfikatorów timerów
- Przechowywania wartości, które nie powinny wywoływać aktualizacji UI
import { useRef, useEffect } from 'react';
function TextInput() {
const inputRef = useRef(null);
useEffect(() => {
// Ustawienie fokusu na polu wejściowym po zamontowaniu komponentu
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
useReducer: Zarządzanie złożoną logiką stanu
Dla stanu obejmującego wiele podwartości lub złożone przejścia, useReducer jest często lepszy niż useState. Przyjmuje funkcję reducer i stan początkowy, zwracając bieżący stan i funkcję dispatch:
import { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: action.payload };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Licznik: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset', payload: 0 })}>Reset</button>
</>
);
}
Hooks do optymalizacji wydajności
useMemo: Buforowanie kosztownych obliczeń
useMemo buforuje wynik obliczenia i przelicza go tylko przy zmianie zależności:
import { useMemo } from 'react';
function TodoList({ todos, filter }) {
// Przeliczenie tylko przy zmianie todos lub filter
const filteredTodos = useMemo(() => {
return todos.filter(todo => todo.status === filter);
}, [todos, filter]);
return (
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
useCallback: Memoizacja referencji do funkcji
useCallback zwraca zmemoizowaną wersję funkcji zwrotnej, która zmienia się tylko przy zmianie zależności. Jest to szczególnie przydatne przy przekazywaniu callbacków do zoptymalizowanych komponentów potomnych, które polegają na równości referencyjnej, aby zapobiec niepotrzebnym przerenderowaniom:
import { useCallback } from 'react';
function ParentComponent() {
const handleClick = useCallback(() => {
console.log('Przycisk kliknięty');
}, []); // Referencja do funkcji pozostaje stabilna między renderowaniami
return <ChildButton onClick={handleClick} />;
}
Własne Hooks: Ponowne wykorzystanie logiki między komponentami
Prawdziwa moc Hooków ujawnia się przy tworzeniu własnych Hooków do wyodrębniania i ponownego wykorzystywania logiki ze stanem. Własny Hook to po prostu funkcja JavaScript, której nazwa zaczyna się od use i która może wywoływać inne Hooks.
Przykład: useLocalStorage
Ten własny Hook synchronizuje stan z localStorage, zachowując dane po odświeżeniu strony:
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
if (typeof window === 'undefined') return initialValue;
const stored = localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Użycie
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Przełącz na {theme === 'light' ? 'ciemny' : 'jasny'} tryb
</button>
);
}
Przykład: useMediaQuery
Ten Hook umożliwia warunkowe renderowanie komponentów na podstawie zapytań medialnych CSS, np. preferencji użytkownika dotyczących ograniczenia ruchu:
import { useState, useEffect } from 'react';
function useMediaQuery(query) {
const [matches, setMatches] = useState(() => {
if (typeof window === 'undefined') return false;
return window.matchMedia(query).matches;
});
useEffect(() => {
const mediaQueryList = window.matchMedia(query);
const onChange = (event) => setMatches(event.matches);
mediaQueryList.addEventListener('change', onChange);
return () => mediaQueryList.removeEventListener('change', onChange);
}, [query]);
return matches;
}
// Użycie
function MotionAwareComponent() {
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
return prefersReducedMotion ? <StaticHero /> : <AnimatedHero />;
}
React 19: Nowe Hooks dla formularzy i danych asynchronicznych
React 19 (wydany w grudniu 2024 roku) wprowadził kilka nowych Hooków przeznaczonych do uproszczenia pracy z formularzami i operacjami asynchronicznymi.
useActionState: Uproszczenie wysyłania formularzy
useActionState zarządza stanem wysyłania formularza, zwracając ostatni wynik, funkcję akcji i flagę oczekiwania:
import { useActionState } from 'react';
async function subscribeAction(prevState, formData) {
const email = formData.get('email');
try {
await subscribe(email);
return { ok: true };
} catch (error) {
return { ok: false, error: error.message };
}
}
function NewsletterForm() {
const [state, formAction, isPending] = useActionState(
subscribeAction,
{ ok: false }
);
return (
<form action={formAction}>
<input name="email" type="email" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Subskrypcja...' : 'Subskrybuj'}
</button>
{state.ok && <p>Jesteś zapisany!</p>}
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
</form>
);
}
useFormStatus: Dostęp do stanu formularza nadrzędnego
useFormStatus umożliwia zagnieżdżonym komponentom sprawdzenie, czy ich formularz nadrzędny jest wysyłany:
import { useFormStatus } from 'react';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Zapisywanie...' : 'Zapisz'}
</button>
);
}
Integracja z Redux: Hook useSelector
Podczas używania Redux z React, Hook useSelector jest nowoczesnym zalecanym sposobem dostępu do stanu z magazynu Redux. Zastępuje on stary komponent wyższego rzędu connect i lepiej współpracuje z TypeScript.
import { useSelector, useDispatch } from 'react-redux';
function CounterComponent() {
// Wyodrębnienie konkretnych danych z magazynu Redux
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>Zwiększ</button>
</div>
);
}
Ważne: useSelector domyślnie używa ścisłego porównania referencyjnego ===. Zwracanie nowego obiektu za każdym razem spowoduje przerenderowania. Używaj zmemoizowanych selektorów lub wywołuj useSelector wielokrotnie dla poszczególnych pól, aby zoptymalizować wydajność:
// ❌ To powoduje przerenderowania przy każdej akcji
const { count, user } = useSelector((state) => ({
count: state.count,
user: state.user
}));
// ✅ Wywołuj useSelector dla każdego pola
const count = useSelector((state) => state.count);
const user = useSelector((state) => state.user);
Źródła
- Oficjalna dokumentacja React: Wbudowane React Hooks
- Oficjalna dokumentacja React: Podręcznik API Hooks
- Dokumentacja React Redux: API Hooks
- Patterns.dev: Wzorzec Hooks
- Oficjalna dokumentacja React: Używanie State Hook
— Editorial Team
Brak komentarzy.