Czym jest useImperativeHandle w React?
02/11/2023
3 min read
W tym poście przybliżę Wam jeden z mniej znanych, ale bardzo przydatnych React Hooków -
useImperativeHandle
. Na pierwszy rzut oka, jego nazwa może wydawać się nieco odstraszająca, ale gwarantuję, że po przeczytaniu tego wpisu wszystko stanie się jasne.Czym jest useImperativeHandle?
useImperativeHandle
to hook, który pozwala komponentowi dziecka udostępnić pewne funkcje lub właściwości komponentowi nadrzędnemu, dając mu większą kontrolę nad komponentem dziecka.Domyślnie w React istnieje tylko jednokierunkowy przepływ danych. To znaczy, że propsy mogą być przekazywane tylko z komponentu rodzica do komponentu dziecka. Przez to rodzic nie może wywoływać metod, które są zdefiniowane w komponentach potomnych.
Są sytuacje, które wymagają, aby rodzic mógł sterować elementami komponentu dziecka, np.:
- Sprawdzanie poprawności formularza: Komponent podrzędny może udostępniać funkcję sprawdzającą dane formularza komponentowi nadrzędnemu, który można używać do wyświetlania komunikatów o błędach lub zapobiegania wysyłaniu formularzy.
- Obsługa danych wejściowych użytkownika: Komponent podrzędny może udostępnić funkcję obsługującą dane wejściowe użytkownika komponentowi nadrzędnemu, który można użyć do aktualizacji innych części stanu aplikacji.
- Dostęp do stanu podrzędnego:
useImperativeHandle
można użyć do udostępnienia pewnych wartości stanu komponentu podrzędnego komponentowi nadrzędnemu, umożliwiając komponentowi nadrzędnemu dostęp do tego stanu i korzystanie z niego.
Jak jest skonstruowany?
useImperativeHandle
przyjmuje dwa argumenty: referencję i funkcję tworzącą. Funkcja tworząca zwraca obiekt, który jest dostępny dla rodzica poprzez ref
.Oto podstawowa składnia:
useImperativeHandle(ref, () => {// tutaj można zdefiniować metody}, [deps]);
ref
: Referencja przekazana z komponentu nadrzędnego.- Funkcja, która wystawia dane metody, zmienne z komponentu w
ref
. deps
: Tablica zależności, podobnie jak w innych hookach.
Do czego jest najbardziej przydatny? - realny use case z projektu
Wyobraźmy sobie, że w aplikacji posiadasz komponent video. Posiada miniaturkę, odtwarzacz i przycisk, dzięki któremu możemy włączyć video. Taki komponent można umieścić w dowolnej części aplikacji i będzie działał.
Natomiast przychodzi wymaganie, że komponent video w jednym przypadku ma zostać częścią innego komponentu, np. jakiegoś komponentu
Card
, gdzie jest dodatkowy przycisk, który odtwarza przekazane video.Działając w standardowy sposób musielibyśmy przenieść całą logikę odtwarzania video poza komponent video, do wszystkich komponentów, które z niego korzystają. Nie chcemy tak zrobić, ponieważ właśnie cała logika stojąca za video powinna znajdować się w komponencie.
Więc jak sobie poradzić z takim wymaganiem?
W tym przypadku idealnie sprawdzi się
useImperativeHandle
. W komponencie video, poprzez tego hooka, wystawimy metody do włączania video, przez co nasz CardComponent
będzie miał do nich dostęp.Zobacz przykład:
import React, { useRef, useImperativeHandle, forwardRef } from 'react';const VideoComponent = forwardRef((props, ref) => {const [isVideoPlayed, setIsVideoPlayed] = useState(false);useImperativeHandle(ref, () => ({playVideo: () => {setIsVideoPlayed(true);}}));return <VideoPlayer isVideoPlayed={isVideoPlayed} setIsVideoPlayed={setIsVideoPlayed}/>;});const CardComponent = () => {const videoRef = useRef();const onClick = () => {videoRef.current.playVideo();};return (<><VideoComponent ref={videoRef} /><button onClick={onClick}>Play video</button></>);};
W komponencie
VideoComponent
używamy forwardRef
i useImperativeHandle
, aby "wystawić" metodę playVideo
do komponentu nadrzędnego. Następnie, w CardComponent
, możemy użyć tej metody bezpośrednio.Dzięki temu rozwiązaniu logika odtwarzania video dalej jest zamknięta w komponencie odpowiedzialnym za to, a zyskaliśmy dodatkowe możliwości włączania video spoza innego komponentu.
Pamiętaj jednak, że
useImperativeHandle
powinien być używany rzadko i z umiarem. Zasada dobrego projektowania mówi, że komponenty powinny być jak najbardziej samodzielne i niezależne od rodzica. Użycie tego hooka może prowadzić do zbyt silnej zależności między komponentami. Zawsze zastanów się dwa razy, zanim zdecydujesz się go użyć!Jeśli możesz wyrazić coś jako props, nie powinieneś używać
useImperativeHandle
. Na przykład zamiast eksponować takie propsy jak { open, close } ze komponentu Modal, lepiej jest przyjąć isOpen jako props <Modal isOpen={isOpen} />
i użyć useEffect
do odpowiedniej synchronizacji