Czym jest useImperativeHandle w React?

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

Zobacz więcej wpisów