Polski

Dowiedz się, jak wykorzystać niestandardowe hooki React do ekstrakcji i ponownego użycia logiki komponentów, poprawiając utrzymanie kodu, testowalność i architekturę aplikacji.

Niestandardowe Hooki React: Ekstrakcja Logiki Komponentów do Ponownego Użycia

Hooki React zrewolucjonizowały sposób pisania komponentów React, oferując bardziej elegancki i wydajny sposób zarządzania stanem i efektami ubocznymi. Wśród dostępnych hooków, niestandardowe hooki wyróżniają się jako potężne narzędzie do ekstrakcji i ponownego użycia logiki komponentów. Ten artykuł stanowi kompleksowy przewodnik po zrozumieniu i implementacji niestandardowych hooków React, umożliwiając tworzenie bardziej łatwych w utrzymaniu, testowalnych i skalowalnych aplikacji.

Czym są Niestandardowe Hooki React?

W istocie, niestandardowy hook to funkcja JavaScript, której nazwa zaczyna się od "use" i która może wywoływać inne hooki. Pozwala ona na ekstrakcję logiki komponentu do funkcji wielokrotnego użytku, eliminując tym samym powielanie kodu i promując czystszą strukturę komponentów. W przeciwieństwie do zwykłych komponentów React, niestandardowe hooki nie renderują żadnego interfejsu użytkownika; po prostu enkapsulują logikę.

Traktuj je jak funkcje wielokrotnego użytku, które mogą uzyskiwać dostęp do funkcji stanu i cyklu życia React. Są fantastycznym sposobem na udostępnianie logiki stanowej między różnymi komponentami, bez uciekania się do komponentów wyższego rzędu (higher-order components) lub render props, które często prowadzą do kodu trudnego do odczytu i utrzymania.

Dlaczego Używać Niestandardowych Hooków?

Korzyści z używania niestandardowych hooków są liczne:

Tworzenie Pierwszego Niestandardowego Hooka

Zilustrujmy tworzenie i używanie niestandardowego hooka praktycznym przykładem: pobieranie danych z API.

Przykład: useFetch - Hook do Pobierania Danych

Wyobraź sobie, że często potrzebujesz pobierać dane z różnych API w swojej aplikacji React. Zamiast powtarzać logikę pobierania w każdym komponencie, możesz stworzyć hook useFetch.


import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const abortController = new AbortController();
    const signal = abortController.signal;

    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch(url, { signal: signal });
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        const json = await response.json();
        setData(json);
        setError(null); // Wyczyszczenie poprzednich błędów
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          setError(error);
        }
        setData(null); // Wyczyszczenie poprzednich danych
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    return () => {
      abortController.abort(); // Funkcja czyszcząca do anulowania żądania fetch przy odmontowaniu lub zmianie URL
    };
  }, [url]); // Ponowne uruchomienie efektu po zmianie URL

  return { data, loading, error };
}

export default useFetch;

Wyjaśnienie:

Używanie Hooka useFetch w Komponencie

Zobaczmy teraz, jak użyć tego niestandardowego hooka w komponencie React:


import React from 'react';
import useFetch from './useFetch';

function UserList() {
  const { data: users, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');

  if (loading) return <p>Ładowanie użytkowników...</p>;
  if (error) return <p>Błąd: {error.message}</p>;
  if (!users) return <p>Nie znaleziono użytkowników.</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name} ({user.email})</li>
      ))}
    </ul>
  );
}

export default UserList;

Wyjaśnienie:

Zaawansowane Wzorce Niestandardowych Hooków

Oprócz prostego pobierania danych, niestandardowe hooki mogą być używane do enkapsulacji bardziej złożonej logiki. Oto kilka zaawansowanych wzorców:

1. Zarządzanie Stanem z useReducer

W przypadku bardziej złożonych scenariuszy zarządzania stanem można połączyć niestandardowe hooki z useReducer. Pozwala to na bardziej przewidywalne i zorganizowane zarządzanie przejściami stanu.


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 };
    default:
      throw new Error();
  }
}

function useCounter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const increment = () => dispatch({ type: 'increment' });
  const decrement = () => dispatch({ type: 'decrement' });

  return { count: state.count, increment, decrement };
}

export default useCounter;

Użycie:


import React from 'react';
import useCounter from './useCounter';

function Counter() {
  const { count, increment, decrement } = useCounter();

  return (
    <div>
      <p>Licznik: {count}</p>
      <button onClick={increment}>Zwiększ</button>
      <button onClick={decrement}>Zmniejsz</button>
    </div>
  );
}

export default Counter;

2. Integracja z Kontekstem za pomocą useContext

Niestandardowe hooki mogą być również używane do uproszczenia dostępu do Kontekstu React. Zamiast bezpośredniego używania useContext w komponentach, można stworzyć niestandardowy hook, który enkapsuluje logikę dostępu do kontekstu.


import { useContext } from 'react';
import { ThemeContext } from './ThemeContext'; // Zakładając, że masz ThemeContext

function useTheme() {
  return useContext(ThemeContext);
}

export default useTheme;

Użycie:


import React from 'react';
import useTheme from './useTheme';

function MyComponent() {
  const { theme, toggleTheme } = useTheme();

  return (
    <div style={{ backgroundColor: theme.background, color: theme.color }}>
      <p>To jest mój komponent.</p>
      <button onClick={toggleTheme}>Przełącz Motyw</button>
    </div>
  );
}

export default MyComponent;

3. Debouncing i Throttling

Debouncing i throttling to techniki używane do kontrolowania częstotliwości wywoływania funkcji. Niestandardowe hooki mogą być używane do enkapsulacji tej logiki, ułatwiając stosowanie tych technik do obsługi zdarzeń.


import { useState, useEffect, useRef } from 'react';

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

export default useDebounce;

Użycie:


import React, { useState } from 'react';
import useDebounce from './useDebounce';

function SearchInput() {
  const [searchValue, setSearchValue] = useState('');
  const debouncedSearchValue = useDebounce(searchValue, 500); // Debounce przez 500ms

  useEffect(() => {
    // Wykonaj wyszukiwanie z debouncedSearchValue
    console.log('Szukam:', debouncedSearchValue);
    // Zastąp console.log faktyczną logiką wyszukiwania
  }, [debouncedSearchValue]);

  const handleChange = (event) => {
    setSearchValue(event.target.value);
  };

  return (
    <input
      type="text"
      value={searchValue}
      onChange={handleChange}
      placeholder="Szukaj..."
    />
  );
}

export default SearchInput;

Najlepsze Praktyki Pisania Niestandardowych Hooków

Aby upewnić się, że Twoje niestandardowe hooki są skuteczne i łatwe w utrzymaniu, przestrzegaj tych najlepszych praktyk:

Globalne Rozważania

Podczas tworzenia aplikacji dla globalnej publiczności, pamiętaj o następujących kwestiach:

Przykład: Zinternacjonalizowane Formatowanie Daty z Niestandardowym Hookiem


import { useState, useEffect } from 'react';
import { DateTimeFormat } from 'intl';

function useFormattedDate(date, locale) {
  const [formattedDate, setFormattedDate] = useState('');

  useEffect(() => {
    try {
      const formatter = new DateTimeFormat(locale, {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
      });
      setFormattedDate(formatter.format(date));
    } catch (error) {
      console.error('Błąd formatowania daty:', error);
      setFormattedDate('Nieprawidłowa Data');
    }
  }, [date, locale]);

  return formattedDate;
}

export default useFormattedDate;

Użycie:


import React from 'react';
import useFormattedDate from './useFormattedDate';

function MyComponent() {
  const today = new Date();
  const enDate = useFormattedDate(today, 'en-US');
  const frDate = useFormattedDate(today, 'fr-FR');
  const deDate = useFormattedDate(today, 'de-DE');

  return (
    <div>
      <p>Data US: {enDate}</p>
      <p>Data Francuska: {frDate}</p>
      <p>Data Niemiecka: {deDate}</p>
    </div>
  );
}

export default MyComponent;

Wniosek

Niestandardowe hooki React to potężny mechanizm do ekstrakcji i ponownego użycia logiki komponentów. Wykorzystując niestandardowe hooki, możesz pisać czystszy, łatwiejszy w utrzymaniu i testowalny kod. W miarę jak będziesz zdobywać większą biegłość w React, opanowanie niestandardowych hooków znacznie poprawi Twoją zdolność do tworzenia złożonych i skalowalnych aplikacji. Pamiętaj, aby przestrzegać najlepszych praktyk i uwzględniać czynniki globalne podczas tworzenia niestandardowych hooków, aby zapewnić ich skuteczność i dostępność dla zróżnicowanej publiczności. Wykorzystaj moc niestandardowych hooków i podnieś swoje umiejętności w zakresie tworzenia aplikacji React!