useEffect
W React 16.8 pojawiły się hooki, które pozwalają m.in. na tworzenie stanowych komponentów funkcyjnych, a także na wykonywanie efektów ubocznych w funkcjach. Dokładnie temu służy hook useEffect
.
Jakie to mogą być efekty uboczne? Wszystko, co dzieje się asynchronicznie lub poza komponentem:
- zapytania do API (fetch)
- subskrypcje (np. rxjs albo EventEmitter)
- timery (
setTimeout
isetInterval
) - aktualizacja
document.title
- nasłuchiwanie na zdarzenia — np.
resize
- …
Żadne z wymienionych rzeczy nie powinny znaleźć się bezpośrednio w komponencie. Zamiast tego, użyj useEffect
!
Pierwszy useEffect
Skorzystamy tutaj z przykładu z poprzedniego wpisu:
Chcesz, aby document.title
był aktualizowany za każdym razem, gdy wpisujesz coś w input i filtrujesz listę użytkowników. Użyjesz wtedy dwóch hooków: useState
i useEffect
:
function App() {
const [filteredUsers, setUsers] = React.useState(allUsers);
function filterUsers(e) {
const text = e.currentTarget.value;
const filteredUsers = getFilteredUsersForText(text);
setUsers(filteredUsers);
}
useEffect(() => {
// 1
document.title = `Showing ${filteredUsers.length} users!`;
});
return (
<div>
<input onInput={filterUsers} />
<UsersList users={filteredUsers} />
</div>
);
}
Jedyne, co się tutaj zmieniło w stosunku do ostatniego wpisu, to 3 nowe linie:
useEffect(() => {
// 1
document.title = `Showing ${filteredUsers.length} users!`;
});
Ten krótki kod sprawia, że przy każdym renderze wywoła się przekazana do useEffect
funkcja i zaktualizowany zostanie document.title
.
Przy każdym renderze?
Tak. Domyślnie, tak. W wielu przypadkach to pożądane zachowanie! Ale tutaj, możesz nieco zoptymalizować. Chciałabyś pewnie, aby nasz efekt wywoływał się wyłącznie wtedy, gdy zmienia się filteredUsers
. To na tyle popularne zastosowanie, że jest wbudowane w useEffect
. Nie trzeba korzystać z componentDidUpdate
i manualnie porównywać zmienionych propsów. Zamiast tego…
useEffect(() => {
document.title = `Showing ${filteredUsers.length} users!`;
}, [filteredUsers]); // 2
Dodaję do useEffect
drugi argument. Jest to tablica wartości, na podstawie których React podejmuje decyzję, czy dany efekt wywołać ponownie, czy nie.
Jeśli podajesz tablicę jako argument do useEffect
, to koniecznie zwróć uwagę, aby zawierała ona listę wszystkich wartości z komponentu, które są używane w samym hooku.
Demo: React Hooks w przykładach: useEffect.
Sprzątanie po sobie
Bardzo częstym wymaganiem jest, dodatkowo, wykonanie jakiejś akcji (jakiegoś „efektu”), gdy trzeba po sobie posprzątać. Na przykład, gdy komponent jest odmontowywany, albo po prostu przerenderowywany. Ten przypadek również jest obsłużonyn przez useEffect
! Wystarczy wewnątrz niego zwrócić funkcję:
useEffect(() => {
const subscription = props.status$.subscribe(handleStatusChange); // 3
return () => {
// 4
subscription.unsubscribe();
};
});
W linii (3) podpinam się pod jakąś subskrypcję — w tym przypadku załóżmy, że jest to strumień rxjs. Moja funkcja „sprzątająca”, którą zwracam (4) usuwa subskrypcję, bo nie będzie już potrzebna.
Pamiętaj, aby zawsze usuwać wszelkie subskrypcje i timery, gdy nie są już potrzebne!
Zwróć uwagę, że subscribe
i unsubscribe
wywoływane są teraz przy każdym renderze! To dobre zachowanie. Pomaga uniknąć subtelnych błędów, gdy zapominamy się ponownie podpiąć pod subskrypcję, gdy ta się zmienia. Dopóki nie sprawia to problemów — ja bym się tym szczególnie nie przejmował.
Są jednak sytuacje, gdy podpinanie subskrypcji na nowo przy każdym renderze jest niedopuszczalne i niepotrzebne. Kiedy? No na przykład wtedy, gdy z subskrypcją związane są jakieś kolejne efekty uboczne — np. skasowanie naszego timera (i przez to pomyłka w odliczaniu czasu), albo wysłanie kolejnego żądania do websocketów z prośbą o odpięcie i potem ponowne podpięcie do nasłuchiwania na zdarzenia.
W takiej sytuacji, jak już pisałem wcześniej, wystarczy jako drugi argument useEffect
przekazać tablicę. W tym przypadku byłoby to:
useEffect(() => {
const subscription = props.status$.subscribe(handleStatusChange);
return () => {
subscription.unsubscribe();
};
}, [props.status$]); // 5
Mówisz tutaj Reactowi: „usuń tę subskrypcję i stwórz ją na nowo tylko wtedy, gdy zmieni się props.status$
”. To genialne ułatwienie i prosta optymalizacja!
A jakby tak klasą?
Porównaj teraz powyższy kod z analogicznym kodem w Reactowej klasie:
componentDidMount() {
this.subscription = props.status$.subscribe(this.handleStatusChange);
}
componentDidUpdate(prevProps) {
if (prevProps.status$ !== this.props.status$) {
this.subscription.unsubscribe();
this.subscription = props.status$.subscribe(this.handleStatusChange);
}
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
Ten kod robi dokładnie to samo, a jest dwukrotnie dłuższy!
Pytania?
zapisz się na szkolenie z React. Jeśli chcesz na bieżąco śledzić kolejne części kursu React.js to koniecznie polub mnie na Facebooku i zapisz się na newsletter.