Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Własne hooki (Custom Hooks) do efektów

W kosmicznej podróży przez React, wielokrotne pisanie tych samych wzorców useEffect w różnych komponentach to jak budowanie od zera identycznych systemów nawigacyjnych dla każdego statku we flocie. Custom Hooks pozwalają wyekstrahować powtarzalną logikę efektów do jednej, wielokrotnie używanej funkcji - jak stworzenie modułu napędowego, który można zainstalować w dowolnym statku.

Czym są Custom Hooks?

Custom Hook to zwykła funkcja JavaScript, której nazwa zaczyna się od

use
i która może wywoływać inne hooki (useState, useEffect, useRef itp.). Nie jest to żadna specjalna składnia Reacta - jedyną konwencją jest prefiks
use
.

1// Custom Hook - wyekstrahowana logika
2function useWindowSize() {
3  const [size, setSize] = useState({
4    width: window.innerWidth,
5    height: window.innerHeight
6  });
7
8  useEffect(() => {
9    const handleResize = () => {
10      setSize({ width: window.innerWidth, height: window.innerHeight });
11    };
12
13    window.addEventListener('resize', handleResize);
14    return () => window.removeEventListener('resize', handleResize);
15  }, []);
16
17  return size;
18}
19
20// Użycie w komponencie - czysto i prosto
21function SpaceshipCockpit() {
22  const { width, height } = useWindowSize();
23
24  return (
25    <p>
26      Rozmiar ekranu kokpitu: {width} x {height}
27    </p>
28  );
29}

Zauważ, że

useWindowSize
zawiera wewnątrz useState i useEffect z cleanup - cała logika jest zamknięta w jednym hooku, a komponent dostaje gotowy wynik.

useFetch - pobieranie danych

Jednym z najczęstszych custom hooków jest hook do pobierania danych. Zamiast powtarzać wzorzec loading/error/data w każdym komponencie, zamykamy go w

useFetch
:

1function useFetch(url) {
2  const [data, setData] = useState(null);
3  const [isLoading, setIsLoading] = useState(true);
4  const [error, setError] = useState(null);
5
6  useEffect(() => {
7    const controller = new AbortController();
8    setIsLoading(true);
9    setError(null);
10
11    fetch(url, { signal: controller.signal })
12      .then(response => {
13        if (!response.ok) throw new Error('Blad sieci');
14        return response.json();
15      })
16      .then(data => {
17        setData(data);
18        setIsLoading(false);
19      })
20      .catch(err => {
21        if (err.name !== 'AbortError') {
22          setError(err.message);
23          setIsLoading(false);
24        }
25      });
26
27    // Cleanup - anuluj request przy odmontowaniu lub zmianie URL
28    return () => controller.abort();
29  }, [url]);
30
31  return { data, isLoading, error };
32}

Teraz każdy komponent potrzebujący danych z API może użyć jednej linii:

1function CrewList() {
2  const { data: crew, isLoading, error } = useFetch('/api/crew');
3
4  if (isLoading) return <p>Ladowanie zalogi...</p>;
5  if (error) return <p>Blad: {error}</p>;
6
7  return (
8    <ul>
9      {crew.map(member => (
10        <li key={member.id}>{member.name}</li>
11      ))}
12    </ul>
13  );
14}
15
16function MissionLog() {
17  const { data: missions, isLoading } = useFetch('/api/missions');
18
19  if (isLoading) return <p>Ladowanie misji...</p>;
20
21  return missions.map(m => <div key={m.id}>{m.name}</div>);
22}

Dwa komponenty, ten sam wzorzec pobierania danych - zero duplikacji kodu.

useInterval - bezpieczny timer

Timer z setInterval wymaga cleanup i poprawnej obsługi zależności. Custom hook zamyka tę logikę:

1function useInterval(callback, delay) {
2  const savedCallback = useRef();
3
4  // Zapamiętaj najnowszą wersję callback
5  useEffect(() => {
6    savedCallback.current = callback;
7  }, [callback]);
8
9  // Ustaw timer
10  useEffect(() => {
11    if (delay === null) return; // null = pauzuj timer
12
13    const tick = () => savedCallback.current();
14    const id = setInterval(tick, delay);
15    return () => clearInterval(id);
16  }, [delay]);
17}
18
19// Uzycie
20function MissionClock() {
21  const [seconds, setSeconds] = useState(0);
22  const [isRunning, setIsRunning] = useState(false);
23
24  useInterval(() => {
25    setSeconds(prev => prev + 1);
26  }, isRunning ? 1000 : null); // null = pauzuj
27
28  return (
29    <div>
30      <p>Czas misji: {seconds}s</p>
31      <button onClick={() => setIsRunning(!isRunning)}>
32        {isRunning ? 'Pauza' : 'Start'}
33      </button>
34    </div>
35  );
36}

Dzięki

useRef
dla callback, timer zawsze wywołuje najnowszą wersję funkcji bez konieczności restartowania interwału.

useLocalStorage - trwały stan

Hook synchronizujący stan z localStorage:

1function useLocalStorage(key, initialValue) {
2  const [storedValue, setStoredValue] = useState(() => {
3    try {
4      const item = window.localStorage.getItem(key);
5      return item ? JSON.parse(item) : initialValue;
6    } catch (error) {
7      return initialValue;
8    }
9  });
10
11  useEffect(() => {
12    try {
13      window.localStorage.setItem(key, JSON.stringify(storedValue));
14    } catch (error) {
15      console.error('Blad zapisu do localStorage:', error);
16    }
17  }, [key, storedValue]);
18
19  return [storedValue, setStoredValue];
20}
21
22// Uzycie - dziala jak useState, ale persystuje dane
23function ShipSettings() {
24  const [theme, setTheme] = useLocalStorage('theme', 'dark');
25  const [volume, setVolume] = useLocalStorage('volume', 50);
26
27  return (
28    <div>
29      <select value={theme} onChange={e => setTheme(e.target.value)}>
30        <option value="dark">Ciemny</option>
31        <option value="light">Jasny</option>
32      </select>
33      <input
34        type="range"
35        value={volume}
36        onChange={e => setVolume(Number(e.target.value))}
37      />
38    </div>
39  );
40}

Po odswiezeniu strony ustawienia sa zachowane - localStorage pamięta wartości między sesjami.

Zasady tworzenia Custom Hooks

  1. Nazwa zaczyna się od
    use
    - to nie jest opcjonalne. React wymaga tego prefiksu, aby móc sprawdzać reguły hooków
  2. Mogą wywoływać inne hooki - useState, useEffect, useRef, a nawet inne custom hooki
  3. Każde wywołanie ma własny stan - dwa komponenty używające tego samego hooka mają niezależne kopie stanu
  4. Zwracają to, czego potrzebuje komponent - mogą zwracać wartości, tablice, obiekty, funkcje
1// Custom Hook z wieloma wartościami zwracanymi
2function useToggle(initialValue = false) {
3  const [value, setValue] = useState(initialValue);
4
5  const toggle = () => setValue(prev => !prev);
6  const setTrue = () => setValue(true);
7  const setFalse = () => setValue(false);
8
9  return { value, toggle, setTrue, setFalse };
10}
11
12// Uzycie
13function ShieldControl() {
14  const shields = useToggle(false);
15
16  return (
17    <div>
18      <p>Tarcze: {shields.value ? 'AKTYWNE' : 'WYLACZONE'}</p>
19      <button onClick={shields.toggle}>Przelacz</button>
20      <button onClick={shields.setTrue}>Aktywuj</button>
21      <button onClick={shields.setFalse}>Dezaktywuj</button>
22    </div>
23  );
24}

Podsumowanie

Custom Hooks to jedno z najpotężniejszych narzędzi w React do organizacji kodu:

  1. Eliminują duplikację - powtarzalna logika efektów w jednym miejscu
  2. Poprawiają czytelność - komponenty stają się krótsze i bardziej deklaratywne
  3. Ułatwiają testowanie - logikę hooka można testować niezależnie od komponentu
  4. Są kompozycyjne - custom hooki mogą wywoływać inne custom hooki

Jak modułowe systemy statku kosmicznego - system nawigacyjny, system komunikacyjny, system obronny - każdy jest osobnym modułem (hookiem), który można zainstalować w dowolnym statku (komponencie).

Vai a CodeWorlds