Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Tworzenie własnych hooków (Custom Hooks) - zaawansowane wzorce

W kosmicznej flocie React, jednym z najcenniejszych umiejętności inżyniera jest projektowanie modułów wielokrotnego użytku. Wyobraź sobie, że budujesz systemy nawigacyjne, komunikacyjne i diagnostyczne dla setek statków kosmicznych. Zamiast pisać każdy system od zera dla każdego statku, tworzysz uniwersalne moduły, które można podłączyć do dowolnego pojazdu. Dokładnie tak działają Custom Hooks w React.

W poprzedniej lekcji poznaliśmy podstawy custom hooków - useFetch, useInterval, useLocalStorage i useToggle. Teraz zagłębimy się w zaawansowane wzorce, reguły hooków i kompozycję, które uczynią Twoje hooki naprawdę profesjonalnymi.

Reguły Hooków (Rules of Hooks)

Zanim zaczniemy tworzyć zaawansowane hooki, musimy znać dwie fundamentalne reguły, których złamanie powoduje nieprzewidywalne błędy:

Reguła 1: Wywoływanie hooków tylko na najwyższym poziomie

Hooki muszą być wywoływane zawsze w tej samej kolejności przy każdym renderowaniu. Nigdy nie umieszczaj hooków wewnątrz warunków, pętli ani zagnieżdżonych funkcji:

1// ZLE - hook wewnatrz warunku
2function SpaceShip({ hasRadar }) {
3  if (hasRadar) {
4    const [signal, setSignal] = useState(null); // BLAD!
5  }
6}
7
8// DOBRZE - warunek wewnatrz hooka
9function SpaceShip({ hasRadar }) {
10  const [signal, setSignal] = useState(null);
11
12  useEffect(() => {
13    if (hasRadar) {
14      // logika radaru
15    }
16  }, [hasRadar]);
17}

React śledzi hooki na podstawie ich kolejności wywołania. Jeśli warunek spowoduje pominięcie jednego hooka, wszystkie następne będą miały błędne wartości - jak gdyby przesunęły się wszystkie moduły w statku kosmicznym.

Reguła 2: Wywoływanie hooków tylko z komponentów React lub custom hooków

Nie możesz wywoływać hooków z zwykłych funkcji JavaScript. Tylko:

  • Komponenty funkcyjne React
  • Custom Hooki (funkcje zaczynające się od
    use
    )
1// ZLE - zwykla funkcja
2function calculateTrajectory() {
3  const [speed] = useState(0); // BLAD!
4}
5
6// DOBRZE - custom hook
7function useTrajectory() {
8  const [speed, setSpeed] = useState(0);
9  return { speed, setSpeed };
10}

useDebounce - opóźnianie akcji

Hook

useDebounce
jest niezwykle przydatny do wyszukiwania w czasie rzeczywistym. Zamiast wysyłać zapytanie do API po każdym naciśnięciu klawisza, czekamy aż użytkownik skończy pisać:

1function useDebounce(value, delay) {
2  const [debouncedValue, setDebouncedValue] = useState(value);
3
4  useEffect(() => {
5    const timer = setTimeout(() => {
6      setDebouncedValue(value);
7    }, delay);
8
9    return () => clearTimeout(timer);
10  }, [value, delay]);
11
12  return debouncedValue;
13}
14
15// Uzycie - wyszukiwanie planet
16function PlanetSearch() {
17  const [query, setQuery] = useState('');
18  const debouncedQuery = useDebounce(query, 500);
19
20  useEffect(() => {
21    if (debouncedQuery) {
22      // Zapytanie wysylane dopiero po 500ms przerwy w pisaniu
23      fetch(\`/api/planets?search=\${debouncedQuery}\`);
24    }
25  }, [debouncedQuery]);
26
27  return (
28    <input
29      value={query}
30      onChange={e => setQuery(e.target.value)}
31      placeholder="Szukaj planet..."
32    />
33  );
34}

Timer jest czyszczony przy każdej zmianie wartości. Dopiero gdy użytkownik przestanie pisać na 500ms, wartość zostanie zaktualizowana i zapytanie API wysłane.

usePrevious - zapamiętywanie poprzedniej wartości

Ten prosty hook pozwala porównywać aktualną wartość z poprzednią:

1function usePrevious(value) {
2  const ref = useRef();
3
4  useEffect(() => {
5    ref.current = value;
6  }, [value]);
7
8  return ref.current;
9}
10
11// Uzycie - monitorowanie zmian predkosci
12function SpeedMonitor() {
13  const [speed, setSpeed] = useState(0);
14  const previousSpeed = usePrevious(speed);
15
16  const trend = speed > previousSpeed
17    ? 'Przyspieszanie'
18    : speed < previousSpeed
19    ? 'Hamowanie'
20    : 'Stala predkosc';
21
22  return <p>{trend}: {speed} km/s (bylo: {previousSpeed})</p>;
23}

useArray - zarządzanie tablicami

Tablice wymagają specyficznych operacji (dodawanie, usuwanie, filtrowanie). Custom hook zamyka te operacje w czytelny interfejs:

1function useArray(initialArray = []) {
2  const [array, setArray] = useState(initialArray);
3
4  const push = (element) => setArray(a => [...a, element]);
5  const removeByIndex = (index) =>
6    setArray(a => a.filter((_, i) => i !== index));
7  const update = (index, newElement) =>
8    setArray(a => a.map((item, i) => (i === index ? newElement : item)));
9  const clear = () => setArray([]);
10  const filter = (fn) => setArray(a => a.filter(fn));
11
12  return { array, set: setArray, push, removeByIndex, update, clear, filter };
13}
14
15// Uzycie - lista zaloganow
16function CrewManager() {
17  const crew = useArray(['Kapitan Nova', 'Nawigator Rex']);
18
19  return (
20    <div>
21      {crew.array.map((member, i) => (
22        <div key={i}>
23          {member}
24          <button onClick={() => crew.removeByIndex(i)}>Usun</button>
25        </div>
26      ))}
27      <button onClick={() => crew.push('Nowy czlonek')}>
28        Dodaj czlonka zalogi
29      </button>
30      <button onClick={crew.clear}>Zwolnij zaloge</button>
31    </div>
32  );
33}

Kompozycja hooków - łączenie modułów

Prawdziwa siła custom hooków ujawnia się, gdy łączymy je ze sobą. Hooki mogą wywoływać inne hooki, tworząc złożone zachowania z prostych bloków:

1// Hook 1: Prosty hook stanu z walidacja
2function useValidatedInput(initialValue, validator) {
3  const [value, setValue] = useState(initialValue);
4  const [error, setError] = useState(null);
5
6  const handleChange = (newValue) => {
7    setValue(newValue);
8    const validationError = validator(newValue);
9    setError(validationError);
10  };
11
12  return { value, error, onChange: handleChange, isValid: !error };
13}
14
15// Hook 2: Kompozycja - formularz z wieloma polami
16function useSpaceshipForm() {
17  const name = useValidatedInput('', (v) =>
18    v.length < 3 ? 'Min. 3 znaki' : null
19  );
20  const fuel = useValidatedInput(100, (v) =>
21    v < 0 || v > 100 ? 'Zakres 0-100' : null
22  );
23
24  const isFormValid = name.isValid && fuel.isValid;
25
26  const reset = () => {
27    name.onChange('');
28    fuel.onChange(100);
29  };
30
31  return { name, fuel, isFormValid, reset };
32}
33
34// Uzycie
35function ShipRegistration() {
36  const form = useSpaceshipForm();
37
38  return (
39    <form>
40      <input
41        value={form.name.value}
42        onChange={e => form.name.onChange(e.target.value)}
43      />
44      {form.name.error && <span>{form.name.error}</span>}
45
46      <input
47        type="number"
48        value={form.fuel.value}
49        onChange={e => form.fuel.onChange(Number(e.target.value))}
50      />
51      {form.fuel.error && <span>{form.fuel.error}</span>}
52
53      <button disabled={!form.isFormValid}>Zarejestruj statek</button>
54    </form>
55  );
56}

useSpaceshipForm
używa
useValidatedInput
dwukrotnie - każde wywołanie tworzy niezależną instancję ze swoim stanem.

Wzorzec: useToggle, useCounter, useForm

Popularne wzorce custom hooków, które warto znać:

| Hook | Zastosowanie | Zwraca | |------|--------------|--------| |

useToggle
| Przełącznik on/off |
{ value, toggle, setTrue, setFalse }
| |
useCounter
| Licznik z min/max |
{ count, increment, decrement, reset }
| |
useFetch
| Pobieranie danych |
{ data, loading, error, refetch }
| |
useDebounce
| Opóźnianie wartości | Opóźniona wartość | |
usePrevious
| Poprzednia wartość | Poprzednia wartość | |
useArray
| Operacje na tablicach |
{ array, push, remove, update, clear }
| |
useLocalStorage
| Persystentny stan |
[value, setValue]
|

Podsumowanie

Custom Hooks to fundament dobrze zorganizowanej aplikacji React:

  1. Reguły hooków - zawsze na najwyższym poziomie, tylko w komponentach i hookach
  2. Prefiks
    use
    - obowiązkowy, pozwala Reactowi weryfikować reguły
  3. Kompozycja - hooki mogą wywoływać inne hooki, budując złożone zachowania
  4. Niezależne instancje - każde wywołanie hooka ma własny, izolowany stan
  5. Reużywalność - jedna logika, wiele komponentów

Jak modułowe systemy kosmicznego statku - każdy hook to samodzielny moduł, który można zainstalować, skonfigurować i połączyć z innymi modułami w dowolnej konfiguracji.

Vai a CodeWorlds