Dokładniejsze spojrzenie na najpopularniejsze haki React
Paweł Rybczyński
Software Engineer
W trakcie wielu rozmów zauważyłem, że nawet doświadczeni programiści mają problem z rozróżnieniem Hooków, nie wspominając już o ich bardziej zaawansowanych możliwościach. W tym artykule postaram się więc wyjaśnić, w jaki sposób Hooki powinny być wykorzystywane.
Najważniejsze rzeczy, które należy pamiętać o Hooks:
mogą być używane tylko w komponentach funkcyjnych - komponenty klasowe mają własną implementację cyklu życia;
zawsze zaczynają się od użycie;
Możesz używać dowolnej liczby haków, ale musisz pamiętać, że korzystanie z nich ma wpływ na ogólną wydajność;
muszą być wykonywane w tej samej kolejności przy każdym renderowaniu... ale dlaczego? Spójrzmy na przykład:
<code>srcFunctionComponent.js
Linia 11:5: React Haczyk "useEffect" jest wywoływany warunkowo. <strong>React Haki</strong> muszą być wywoływane dokładnie w tej samej kolejności w każdym komponencie render .eslintreact-hooks/rules-of-hooks
Jak widać, jest to tylko ostrzeżenie eslint, więc można je wyłączyć, dodając polecenie od dołu na górze pliku FunctionComponent
/* eslint-disable react-hooks/rules-of-hooks */
i będzie działać, ale tylko do momentu spełnienia warunku, który uruchamia nasz Hook. Następną rzeczą, którą zobaczymy, jest ten błąd.
Niewykryty błąd: Wyrenderowano więcej haków niż podczas poprzedniego renderowania.
React 5
FunctionComponent FunctionComponent.js:11
React 12
unstable_runWithPriority scheduler.development.js:468
React 17
js index.js:7
js main.chunk.js:905
Webpack 7
react-dom.development.js:15162
Dlaczego tak się dzieje? React polega na kolejności wywoływania haków, ponieważ React nie wiedziałby, co zwrócić dla useEffect, ponieważ nie było takiego haka w linii do sprawdzenia.
Pamiętaj, że eslint to potężne narzędzie, które pomaga nam wyłapać wiele potencjalnych błędów. Wyłączenie jego ostrzeżeń jest niebezpieczne, zawsze sprawdzaj, czy zignorowanie ostrzeżenia może spowodować awarię aplikacji.
useState
Pewnie wiesz jak to wygląda 😉
const [value, setValue] = useState(0);
Mamy więc 4 elementy: stan (wartość reaktywna), funkcja aktualizacji (setter), rzeczywisty hak (funkcja) i opcjonalna wartość początkowa. Dlaczego zwraca tablicę? Ponieważ możemy ją dowolnie restrukturyzować.
Teraz chciałbym skupić się na ostatnim elemencie - wartości początkowej. Istnieją dwa sposoby przekazania stanu początkowego:
Przez zakodowaną wartość lub coś innego - co będzie wywoływane przy każdym renderowaniu
const [value, setValue] = useState(0);
Przez wersję funkcji. Jest to bardzo pomocne, jeśli chcemy uruchomić stan początkowy tylko raz, przy pierwszym renderowaniu. Może trzeba wykonać wiele skomplikowanych obliczeń, aby otrzymać stan początkowy? To ładnie zmniejszy koszt zasobów, yay!
Kolejna rzecz, która może powodować błędy w przepływie aplikacji: prawdopodobnie wiesz, jak zaktualizować stan, prawda?
setValue(1);
Racja... ale co jeśli chcę zaktualizować stan na podstawie poprzedniego stanu?
setValue(value + 1);
Tak... Ale nie... Co jeśli spróbujesz wywołać funkcję setter dwa razy, jeden po drugim? Zalecanym sposobem aktualizacji stanu na podstawie poprzedniego stanu jest użycie funkcji. Gwarantuje to, że odwołujesz się do poprzedniego stanu
Ten hak przyjmuje 2 argumenty (drugi jest opcjonalny) i używamy go do obsługi efektów ubocznych. W zależności od tego, co przekażemy jako drugi argument, hak zostanie wywołany inaczej:
bez drugiego argumentu - każdy render
useEffect(() => {
doSomething();
});
pusta tablica - tylko przy pierwszym renderowaniu
useEffect(() => {
doSomething();
}, []);
tablica z zależnościami - za każdym razem, gdy zmienia się wartość w tablicy zależności
Dzięki useEffect możemy użyć czegoś, co nazywa się cleanup. Do czego to służy? Jest to bardzo przydatne, ale myślę, że najlepiej nadaje się do czyszczenia detektorów zdarzeń. Załóżmy, że chcemy stworzyć event listener, który zależy od jakiegoś stanu. Nie chcesz dodawać nowego detektora zdarzeń przy każdej zmianie stanu, ponieważ po kilku renderowaniach będzie tak wiele detektorów, że wpłynie to na wydajność aplikacji. Świetnym sposobem na uniknięcie takich rzeczy jest użycie funkcji czyszczenia. Jak to zrobić? Wystarczy dodać funkcję zwrotną do useEffect.
Ponieważ jest to wewnątrz haka useEffect, zwracanie jest wywoływane w zależności od tablicy zależności - przy każdym renderowaniu, tylko przy pierwszym renderowaniu lub gdy zmienia się wartość w tablicy zależności. Ale kiedy komponent zostanie odmontowany, czyszczenie zostanie wywołane na drugim argumencie bez względu na wszystko. Zwrot kod jest wywoływana przed właściwym kodem z Hook. To bardzo logiczne - najpierw wyczyść stary, a następnie utwórz nowy. Prawda?
Na początku wygląda to dobrze. Pobierasz jakieś dane, a gdy dane nadejdą, aktualizujesz stan. I tu jest pułapka:
Czasami otrzymasz takie ostrzeżenie: Nie można wykonać aktualizacji stanu React na niezamontowanym komponencie. Jest to sytuacja bezczynności, ale wskazuje na wyciek pamięci w aplikacji. Aby to naprawić, anuluj wszystkie subskrypcje i zadania asynchroniczne w funkcji czyszczenia useEffect.
Powodem jest to, że komponent może zostać odmontowany w międzyczasie, ale aplikacja nadal będzie próbowała zaktualizować stan tego komponentu po spełnieniu obietnicy. Jak sobie z tym poradzić? Należy sprawdzić, czy komponent istnieje.
Uwaga: Istnieje bardzo podobny Hook => useLayoutEffect() - wywołanie zwrotne działa po wyrenderowaniu komponentu, ale przed wizualną aktualizacją domeny. Jest to przydatne podczas pracy z getBoundingClientRect(), ale domyślnie powinieneś używać useEffect. Dlaczego? Ponieważ może blokować aktualizacje wizualne - gdy masz złożony kod wewnątrz efektu Hook.
useContext
Do czego to służy? Udostępnianie danych bez przekazywania rekwizytów. Składa się z następujących elementów:
Utworzony kontekst - dane
Dostawca kontekstu - zapewnia kontekst wszystkim dzieciom
Przekazana wartość - dane, które chcesz udostępnić
W child należy zaimportować kontekst, wywołać hak useContext i przekazać ten kontekst jako argument.
import { UserContext } z "./App";
const { name } = useContext(UserContext);
return <h1>Cześć {imię}<>
```
Voila. Wygląda fajnie. Głównie do przekazywania danych globalnych, takich jak motywy itp. Nie zaleca się używania w zadaniach z bardzo dynamicznymi zmianami.
Oczywiście możemy utworzyć niestandardowego dostawcę kontekstu i niestandardowy hak, aby zamiast tego zredukować boilerplate... ale niestandardowymi hakami zajmę się w następnym artykule.
useReducer
Pozwala nam zarządzać stanem i ponownie renderować, gdy stan się zmieni - jak useState. Jest podobny do reduktora redux. Jest to lepsze rozwiązanie niż useState, gdy logika stanu jest bardziej skomplikowana.
Kiedy go używać? Gdy chcemy osiągnąć równość referencyjną (zmniejszając tym samym liczbę tworzonych funkcji). Hook ten zwraca funkcję, w przeciwieństwie do useMemo, który zwraca wartość.
Przykład: Utwórz funkcję w komponencie nadrzędnym, a następnie przekaż ją za pośrednictwem rekwizytów
Będzie logować się do konsoli przy każdym renderowaniu! Nawet jeśli wartości wewnątrz getSquaredValue() nie uległa zmianie. Możemy jednak tego uniknąć, opakowując tę funkcję w useCallback
Nie jest to neutralne patrząc na koszty zasobów - useMemo musi być wywoływane przy każdym renderowaniu, zapisuje wartość w pamięci i porównuje (narzut pamięci),
wykorzystuje Memoization - technikę optymalizacji, specyficzną formę buforowania.
Powinieneś go używać tylko w 2 scenariuszach:
Jeśli chcesz uniknąć wywoływania złożonego kodu przy każdym renderowaniu;
Jeśli chcesz osiągnąć równość referencyjną.
Przyjrzyjmy się nieco bliżej drugiemu przypadkowi. Chcemy użyć useEffect z obiektem jako zależnością. Ponieważ obiekty są porównywane przez referencję, useEffect będzie wywoływane przy każdym renderowaniu. Aby tego uniknąć, możemy połączyć useEffect z useMemo, aby zapamiętać takie obiekty, a następnie przekazać zapamiętane obiekty do tablicy zależności. Krótki przykład:
Obiekt "hobbit" sprawia, że zależności haka useEffect (linia 49) zmieniają się przy każdym renderowaniu. Przenieś go wewnątrz wywołania zwrotnego useEffect. Alternatywnie, zawiń inicjalizację "hobbita" w jego własny hak useMemo ().eslintreact-hooks/exhaustive-deps
Najważniejsza rzecz: useRef nie wywołuje ponownego renderowania (jak useState), ponieważ nie jest powiązany z cyklem renderowania - zachowuje tę samą referencję między renderowaniami.
const ref = useRef(0);
Aby wywołać zapisaną wartość, należy użyć właściwości current (ref jest obiektem) - ref.current
Drugim przypadkiem, w którym możemy użyć tego haka, jest odniesienie do elementów wewnątrz HTML. Każdy element ma atrybut ref. Możemy więc obsługiwać fokus, zdarzenia itp.
Trzecim przypadkiem jest użycie referencji do obsługi niekontrolowanych komponentów. Więcej na ich temat można przeczytać w dokumenty react, ale w skrócie wygląda to tak:
Jak widać, nie ma obsługi zdarzeń, po prostu zapamiętuje wpisaną wartość. Świetnie nadaje się do obsługi podstawowych formularzy, gdy chcesz po prostu odczytać zapisane wartości, gdy ich potrzebujesz (np. podczas wysyłania).
Bonus: Jest to świetne rozwiązanie, gdy trzeba zapamiętać poprzednie wartości stanu. Możesz użyć do tego haka useEffect, po prostu przekaż stan do ref.
Jak widać, haki nie są takie oczywiste. Możemy je łączyć, aby rozwiązać wiele problemów. Zapoznanie się z tym tematem z pewnością przyniesie wiele korzyści.
Istnieją również niestandardowe haki...
Podsumowując, Haki React zrewolucjonizowały sposób Programiści React budowanie podejścia aplikacje internetowe . Zapewniając bardziej intuicyjny i wydajny sposób zarządzania stanem i cyklem życia w komponentach funkcjonalnych, haki stały się integralną częścią Rozwój React .
Niezależnie od tego, czy jesteś doświadczonym programistą, czy dopiero zaczynasz pracę z React, zrozumienie najpopularniejszych haków i ich przypadków użycia ma kluczowe znaczenie. Z hakami takimi jak useState, useEffect, useContext i nie tylko, Komponenty React może być zbudowany z czystszego i bardziej wielokrotnego użytku kodu. Co więcej, możliwość tworzenia niestandardowe haki umożliwia programistom hermetyzację i współdzielenie logiki w wielu komponentach, promując ponowne wykorzystanie kodu i modułowość. Ponieważ React nadal ewoluuje i wprowadza nowe funkcje, haki niewątpliwie odegrają kluczową rolę w wykorzystaniu pełnego potencjału frameworka.
Tak więc, niezależnie od tego, czy pracujesz nad małą aplikacją funkcyjną, czy dużą aplikacją internetową, przyjęcie Haki React usprawni przepływ pracy programisty i odblokuje mnóstwo możliwości tworzenia solidnych i bogatych w funkcje aplikacji. Zastosowania React .