Początkowo React używał głównie komponentów klasowych, co było męczące. React hooks pozwalają teraz robić to wszystko w komponentach funkcyjnych. Zobacz najciekawsze pytania i odpowiedzi o React Hooks, które pomogą Ci w rozmowie kwalifikacyjnej.
Co to są React Hooks?
React Hooks to funkcjonalność wprowadzona w React 16.8, która umożliwia korzystanie ze stanu (state) i innych cech Reacta bez konieczności tworzenia komponentów klasowych. Dzięki Hooks możesz:
- Wydzielić logikę odpowiadającą zarządzaniu stanem z komponentu, co umożliwia jej niezależne testowanie i ponowne wykorzystanie.
- Używać dedykowanych funkcji do zarządzania stanem i efektami ubocznymi w komponentach funkcyjnych.
- Utrzymać hierarchię komponentów bez modyfikowania jej struktury, co ułatwia ponowne wykorzystanie logiki stanu zarówno w obrębie jednego projektu, jak i w szerszej społeczności.
Ten sposób podejścia znacznie upraszcza zarządzanie logiką aplikacji, eliminując potrzebę korzystania z bardziej złożonych rozwiązań, takich jak HOC (Higher-Order Components) czy render props.
Przydatne materiały:
- Oficjalna dokumentacja React dotycząca Hooks
- Wprowadzenie do React Hooks na blogu React
- Książka: "Learning React" autorstwa Alex Banks i Eve Porcello – rozdziały dotyczące Hooks oferują solidne wprowadzenie do tego tematu.
Jak uzyskać dostęp do elementów DOM w React?
Jedną z przydatnych zastosowań hooka useRef()
jest umożliwienie dostępu do elementów DOM w komponencie. Proces ten
przebiega w trzech krokach:
-
Definicja referencji: Utwórz referencję, która będzie służyć do identyfikacji elementu, np.
const elementRef = useRef();
-
Przypisanie referencji do elementu: Przekaż referencję do atrybutu
ref
wybranego elementu, np.<div ref={elementRef}></div>
-
Odwołanie się do elementu: Po zamontowaniu komponentu, wartość
elementRef.current
będzie wskazywać na docelowy element DOM.
Poniższy przykład ilustruje opisany mechanizm:
import {useRef, useEffect} from 'react';
function AccessingElement() {
const elementRef = useRef();
useEffect(() => {
const divElement = elementRef.current;
console.log(divElement); // wyświetli: <div>I'm an element</div>
}, []);
return (
<div ref={elementRef}>
I'm an element
</div>
);
}
Dla pogłębienia wiedzy oraz uzyskania dodatkowych informacji, warto zajrzeć do oficjalnej dokumentacji React dotyczącej Refs and the DOM. Polecamy również artykuły na temat zaawansowanych zagadnień związanych z React Hooks, takie jak 27 Advanced React Hooks Interview Questions – publikacja ta może stanowić wartościowe źródło wiedzy dla programistów pracujących z React.
Jak wywołać funkcję ładowania przy użyciu hooka Reacta useEffect
tylko raz, tj. po początkowym renderowaniu komponentu?
Aby uruchomić funkcję ładowania wyłącznie podczas montowania komponentu (czyli po pierwszym renderowaniu), należy
przekazać do hooka useEffect
drugi argument – pustą tablicę []
. Dzięki temu efekt zostanie wykonany tylko raz, gdy
komponent zostanie zamontowany. Oto przykładowy fragment kodu:
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // Funkcja wywoływana tylko raz
}, []); // Pusta tablica jako drugi argument gwarantuje jednokrotne wywołanie efektu
return <div>{ /* Reszta komponentu */}</div>;
}
Dodatkowe materiały:
- Dokumentacja React – useEffect
- Artykuł na FullStack.Cafe dotyczący hooków w React
- Książka: "Learning React: Modern Patterns for Developing React Apps" – świetne źródło wiedzy dla programistów chcących pogłębić zagadnienia związane z hookami i komponentami funkcyjnymi.
Poniżej znajduje się przykładowe tłumaczenie pytania wraz z odpowiedzią na język polski, przy użyciu profesjonalnego języka programistycznego i IT.
Podaj przykład prostego niestandardowego (custom) React Hooka.
Niestandardowy Hook w React to funkcja posiadająca stan, która wykorzystuje wbudowane Hooki (np. useState
,
useCallback
itd.). Umożliwia on skupienie logiki związanej ze stanem w jednym miejscu, co pozwala uniknąć powielania
tego samego kodu w różnych komponentach.
Przykładowo, rozważmy Hook odpowiadający za inkrementację i dekremetację:
import {useState} from 'react';
const useCounter = () => {
const [counter, setCounter] = useState(0);
return {
counter, // wartość countera
increment: () => setCounter(counter + 1), // funkcja inkrementująca
decrement: () => setCounter(counter - 1) // funkcja dekrementująca
};
};
Następnie, możemy go wykorzystać w komponencie w następujący sposób:
import React from 'react';
const Component = () => {
const {counter, increment, decrement} = useCounter();
return (
<div>
<span onClick={decrement}>-</span>
<span style={{padding: "10px"}}>{counter}</span>
<span onClick={increment}>+</span>
</div>
);
};
export default Component;
Dlaczego potrzebujemy niestandardowych Hooków?
Niestandardowe Hooki pozwalają na:
- Reużywalność logiki: Dzięki nim możliwe jest wyodrębnienie logiki stanu niezależnie od struktury komponentu, co sprzyja ponownemu użyciu tego samego kodu w różnych częściach aplikacji.
- Redukcję duplikacji: Zamiast kopiować i wklejać tę samą logikę w wielu komponentach, mamy możliwość umieszczenia jej w jednym miejscu.
- Lepszą organizację kodu: Pozwalają skondensować funkcjonalność w mniejszych, łatwiejszych do zarządzania fragmentach, co znacząco ułatwia utrzymanie i rozwój kodu.
Przydatne materiały:
- Oficjalna dokumentacja React – Hooki
- Artykuł: 27 Advanced React Hooks Interview Questions – przykłady i wyjaśnienia dotyczące React Hooków.
- Książka: "Learning React: Modern Patterns for Developing React Apps" – doskonałe źródło wiedzy o nowoczesnych technikach w React.
Czym jest useState()
w React? Wyjaśnij, do czego służy wywołanie useState(0)
w poniższym kodzie:
...
const [count, setCounter] = useState(0);
const [moreStuff, setMoreStuff] = useState(...);
...
const setCount = () => {
setCounter(count + 1);
setMoreStuff(...);
...
};
useState
to jeden z wbudowanych hooków w React. Wywołanie useState(0)
zwraca tablicę (tuple), której pierwszy
element (count
) reprezentuje bieżący stan, a drugi (setCounter
) to funkcja umożliwiająca aktualizację tego stanu.
Korzystając z funkcji setCounter
, możemy w dowolnym miejscu aplikacji modyfikować wartość stanu count
– w powyższym
przykładzie jest to realizowane w funkcji setCount
, gdzie obok aktualizacji stanu licznika wykonywane są inne
operacje. Główną ideą korzystania z hooków jest możliwość pisania bardziej funkcyjnego kodu i unikanie wykorzystywania
komponentów klasowych, jeżeli nie ma takiej konieczności.
Materiały pomocnicze:
-
Dokumentacja React – Hook useState:
React Official Documentation - useState -
Artykuł o hookach w React:
React Hooks – A Complete Introduction -
Książka:
"Learning React: Functional Web Development with React and Redux" – jest to wartościowa pozycja, która omawia zarówno podstawy, jak i zaawansowane zastosowania React, w tym hooki.
Czy można inicjować stan (state) w React za pomocą funkcji? Podaj przykład.
Tak! Poniższy przykład ilustruje inicjalizację stanu przy użyciu funkcji przekazywanej do hooka useState
:
const StateFromFn = () => {
const [token] = useState(() => {
const storedToken = window.localStorage.getItem("my-token");
return storedToken || "default#-token#";
});
return <div>Token is {token}</div>;
};
W powyższym przykładzie funkcja przekazywana do useState
jest wykonywana tylko podczas początkowej inicjalizacji
stanu, co umożliwia np. pobranie tokena z localStorage
i ustawienie wartości początkowej.
Materiały pomocnicze
- Dokumentacja React – useState: React Docs: useState Hook
- Artykuł na temat React Hooks: 27 Advanced React Hooks Interview Questions
- Książka: "Pure React" – świetna pozycja dla zrozumienia działania hooków i komponentów funkcyjnych w React.
Czy dwa komponenty korzystające z tego samego Hooka dzielą stan?
Nie. Niestandardowe Hooki (custom Hooks) to mechanizm umożliwiający ponowne wykorzystanie logiki stanowej (np. konfiguracji subskrypcji czy zapamiętywania aktualnej wartości). Jednak przy każdym użyciu niestandardowego Hooka cały stan oraz efekty zadeklarowane w jego obrębie są całkowicie izolowane.
Dodatkowe materiały
- Oficjalna dokumentacja React – Hooks
- Artykuł o niestandardowych Hookach na FullStack.Cafe
- React – Dokumentacja dotycząca zarządzania stanem przy użyciu useState
Wyjaśnij różnicę między hookami useState i useRef.
- Aktualizacja referencji utworzonej przez useRef nie powoduje ponownego renderowania komponentu, podczas gdy zmiana stanu za pomocą setState wyzwala ponowne renderowanie.
- useRef zwraca obiekt z własnością current, która przechowuje rzeczywistą wartość, podczas gdy useState zwraca tablicę z dwoma elementami – wartością stanu oraz metodą do jego aktualizacji.
- Właściwość current zwróconego obiektu przez useRef jest mutowalna, natomiast zmienna stanu uzyskana z * useState* jest niemutowalna.
- Aktualizacja referencji (poprzez useRef) odbywa się synchronicznie – zaktualizowana wartość jest dostępna od razu, podczas gdy aktualizacja stanu (poprzez useState) jest asynchroniczna – stan zostaje zaktualizowany po kolejnym renderowaniu komponentu.
Przykład użycia useRef (bez wywoływania renderowania):
const countRef = useRef(0);
const handle = () => {
countRef.current++;
console.log(`Clicked ${countRef.current} times`);
};
Przykład użycia useState (trigger re-render):
const [count, setCount] = useState(0);
const handle = () => {
const updatedCount = count + 1;
console.log(`Clicked ${updatedCount} times`);
setCount(updatedCount);
};
Dodatkowe materiały:
- Oficjalna dokumentacja React dotycząca hooków – szczegółowy opis oraz przykłady użycia useState i useRef.
- Artykuł na FullStack.Cafe: Advanced React Hooks Interview Questions – źródło wielu zaawansowanych pytań dotyczących hooków w React.
- Książka "Learning React" autorstwa Alex Banks i Eve Porcello – doskonała lektura dla programistów pragnących pogłębić wiedzę o React oraz hookach.
Jak mogę wykorzystać Error Boundaries w funkcjonalnych komponentach Reacta?
Od wersji 16.2.0 nie istnieje możliwość, aby funkcjonalny komponent pełnił rolę error boundary. Metoda
componentDidCatch()
działa analogicznie do bloku catch {}
w JavaScript, jednakże jest wykorzystywana w kontekście
komponentów. Tylko komponenty klasowe mogą być error boundaries. W praktyce zazwyczaj definiuje się komponent error
boundary raz i stosuje się go w całej aplikacji.
Należy również pamiętać, że bloki try/catch
nie pokrywają wszystkich przypadków. Jeżeli komponent znajdujący się
głęboko w hierarchii spróbuje zaktualizować swój stan i wystąpi błąd, blok try/catch
w komponencie nadrzędnym nie
zadziała – ponieważ aktualizacja nie odbywa się synchronicznie razem z komponentem potomnym.
Warto zwrócić uwagę, że istnieje kilka bibliotek dostępnych na npm, które implementują hooki umożliwiające obsługę error boundaries w funkcjonalnych komponentach.
Dodatkowe materiały:
- Oficjalna dokumentacja Reacta – Error Boundaries
- Artykuł: Effective Error Handling in React Applications – przegląd strategii obsługi błędów w React.
- Książka: React – Up & Running (autor: Stoyan Stefanov), która omawia różne podejścia do budowania aplikacji w React wraz z obsługą błędów.
Jak używać componentWillMount()
w React Hooks?
Nie można korzystać z istniejących metod cyklu życia (takich jak componentDidMount
, componentDidUpdate
,
componentWillUnmount
itd.) w hookach – są one dostępne wyłącznie w komponentach klasowych. W hookach używamy funkcji
useEffect
, która łączy w sobie funkcjonalności componentDidMount
, componentDidUpdate
oraz componentWillUnmount
w
jednym.
-
Kod z
componentDidMount
Aby wykonać kod tylko podczas montowania komponentu, użyj hookauseEffect
wraz z pustą tablicą zależności:
useEffect(() => {
// Twój kod tutaj
}, []);
-
Aktualizacja przy każdorazowym renderowaniu
Jeśli nie podamy drugiego parametru (tablicy zależności), hookuseEffect
zostanie wywołany przy każdym renderowaniu komponentu (podobnie docomponentDidUpdate
), co może prowadzić do nieoczekiwanych konsekwencji:
useEffect(() => {
// Twój kod tutaj
});
-
Obsługa czyszczenia, analogiczna do
componentWillUnmount
Aby zrealizować logikę wywoływaną przy odmontowywaniu komponentu, należy zwrócić funkcję czyszczącą z hookauseEffect
:
useEffect(() => {
window.addEventListener('mousemove', () => {
});
// Funkcja zwracana zostanie wywołana przy odmontowaniu komponentu
return () => {
window.removeEventListener('mousemove', () => {
});
};
}, []);
Materiały referencyjne i przydatne źródła:
-
React Official Documentation - Hooks
Oficjalna dokumentacja React prezentująca zasady działania hooków, w tymuseEffect
. -
React Hooks FAQ
FAQ dotyczące hooków, które mogą pomóc w zrozumieniu ich zastosowania oraz typowych problemów. -
Blog FullStack.Cafe: React Hooks Interview Questions
Artykuł omawiający zaawansowane pytania dotyczące hooków w kontekście rozmów kwalifikacyjnych.
Jakie są typowe przypadki użycia useMemo
?
Głównym celem hooka useMemo
jest optymalizacja wydajności. Dzięki niemu możemy:
- Uzyskać zmemoizowaną wartość,
- Przekazać dwa argumenty: funkcję tworzącą (ang. create function), która zwraca wartość do zmemoizowania, oraz tablicę zależności (dependency array). Wartość zostanie przeliczona ponownie jedynie, gdy przynajmniej jeden z elementów tej tablicy ulegnie zmianie.
Stosowanie useMemo
pozwala na:
- Zachowanie równości referencyjnej wartości (co umożliwia przekazywanie ich jako właściwości do komponentów i potencjalne zapobieganie niepotrzebnym ponownym renderowaniom),
- Eliminację wielokrotnego wykonywania operacji obciążających obliczeniowo, gdy parametry wejściowe pozostają bez zmian.
Przykład użycia
function App() {
const [data, setData] = useState([/* ... dane ... */]);
function format() {
console.log('formatowanie...'); // komunikat pojawi się wyłącznie, gdy zmieni się "data"
const formattedData = [];
data.forEach(item => {
const newItem = /* ... operacje na elemencie ... */;
if (newItem) {
formattedData.push(newItem);
}
});
return formattedData;
}
// Zmemoizowana przetworzona wartość, przeliczana tylko przy zmianie "data"
const formattedData = useMemo(format, [data]);
return (
<>
{formattedData.map(item => (
<div key={item.id}>
{item.title}
</div>
))}
</>
);
}
Dodatkowe materiały i przydatne linki
-
React Official Documentation – useMemo:
React Docs: useMemo -
Pełny artykuł o hookach React, w tym szczegółowa analiza
useMemo
:
FullStack.Cafe – Advanced React Hooks Interview Questions -
Książka o zaawansowanych wzorcach w React:
"Advanced React Patterns" – książka, która pogłębia wiedzę na temat optymalizacji w React.
Jakie są różnice między React.memo()
a useMemo()
?
-
React.memo()
jest wyższego rzędu komponentem (HOC), który można użyć do opakowania komponentów, aby zapobiec ich ponownemu renderowaniu, chyba że zmienią się przekazywane do nich propsy.
Przykłady oraz dokumentację znajdziesz w oficjalnej dokumentacji React. -
useMemo()
to hook Reacta, którego można użyć do opakowania funkcji wewnątrz komponentu. Dzięki niemu można upewnić się, że wyniki obliczeń zawartych w tej funkcji będą ponownie przeliczane tylko wtedy, gdy zmieni się jedna z zależności.
Więcej szczegółów dotyczących hooka znajdziesz w dokumentacji React Hooks.
Jakie są przykłady użycia hooka useRef
?
-
useRef
zwraca prosty obiekt JavaScript (ang. plain Javascript object). Jego wartość można tyle razy odczytywać i modyfikować (mutowalność), ile potrzeba, bez konieczności martwienia się o ponowne renderowanie. - Wartość zwracana przez
useRef
utrzymuje się (nie resetuje się do wartości początkowej, w przeciwieństwie do zwykłego obiektu zadeklarowanego wewnątrz funkcji komponentu; utrzymuje się, ponieważuseRef
zwraca ten sam obiekt zamiast tworzyć nowy przy kolejnych renderach) przez cały okres życia komponentu oraz przy kolejnych renderach. - Hook
useRef
jest często stosowany do przechowywania wartości zamiast referencji do elementów DOM. Mogą to być stany, które nie wymagają częstych zmian, bądź stany, które zmieniają się niezwykle często, ale nie powinny powodować pełnego ponownego renderowania komponentu.
Przykład użycia:
const refObject = useRef(initialValue);
Dodatkowe informacje na temat hooka useRef
znajdziesz
w dokumentacji React.
Jaki jest odpowiednik poniższego kodu przy użyciu React Hooks?
Załóżmy, że w naszym projekcie mamy metodę componentWillUnmount
wykorzystywaną do sprzątania (np. usuwania
nasłuchiwaczy zdarzeń, anulowania timerów itp.). Jak można przekształcić ten kod przy użyciu React Hooks?
Kod w komponencie klasowym:
componentDidMount()
{
window.addEventListener('mousemove', () => {
});
}
componentWillUnmount()
{
window.removeEventListener('mousemove', () => {
});
}
Odpowiednik z wykorzystaniem React Hooks przy użyciu hooka useEffect
:
useEffect(() => {
window.addEventListener('mousemove', () => {
});
// Funkcja zwracana zostanie wywołana przy odmontowaniu komponentu
return () => {
window.removeEventListener('mousemove', () => {
});
};
}, []);
Więcej informacji o hooku useEffect
znajdziesz
w oficjalnej dokumentacji React.
Kiedy stosować hook useContext
?
Hook useContext
w React ułatwia przekazywanie danych w całej aplikacji, bez konieczności ręcznego przekazywania
propsów w dół hierarchii komponentów. React Context stanowi sposób na globalne zarządzanie stanem.
Przykładowe użycie:
import {useState, createContext} from "react";
import ReactDOM from "react-dom/client";
const UserContext = createContext();
Następnie opakowujemy komponenty potomne w Provider Context, dostarczając wartość stanu:
function Component1() {
const [user, setUser] = useState("Jesse Hall");
return (
<UserContext.Provider value={user}>
<h1>{`Hello ${user}!`}</h1>
<Component2 user={user}/>
</UserContext.Provider>
);
}
Dostęp do wartości Context w innych komponentach:
import {useContext} from "react";
function Component5() {
const user = useContext(UserContext);
return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}
Dalsze informacje na temat wykorzystania Contextu w React znajdziesz w dokumentacji React Context.
Kiedy zastosować useRef
?
Główne zastosowania tego hooka to:
-
Przechowywanie referencji do elementów DOM
UżywamyuseRef
do przechowywania referencji, dzięki czemu mamy możliwość późniejszego manipulowania danym elementem (np. ustawiania fokusu).Przykład:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text"/>
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Dodatkowe materiały:
-
Przechowywanie wartości bez wywoływania ponownych renderów
useRef
może służyć do przechowywania zmiennych, które nie powinny wywoływać ponownego renderowania komponentu, np. przechowywanie poprzedniej wartości stanu.Przykład:
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Obecnie: {count}, wcześniej: {prevCount}</h1>;
}
Dodatkowe artykuły:
- React Hooks – pełny przewodnik
- „Pure React” – książka wprowadzająca w zagadnienia związane z hookami w React
Jaka jest różnica między Custom Hook a zwykłą funkcją?
Custom Hook to funkcja, która wykorzystuje wewnętrznie inne wbudowane hooki (np. useState
, useEffect
), dzięki czemu
posiada stateful closure utrzymywane przez React, co pozwala na zarządzanie stanem związanym z konkretną instancją
komponentu. Główne różnice to:
-
Stanowość:
- Custom Hook będzie "stateful" tylko wtedy, gdy użyje hooka
useState
(lub innego implementującego mechanizm stanu) wewnątrz siebie.
- Custom Hook będzie "stateful" tylko wtedy, gdy użyje hooka
-
Ograniczenie wywołań:
- Hooki powinny być wywoływane wyłącznie z kodu React (np. wewnątrz komponentów funkcyjnych) i nie mogą być używane w zwykłych funkcjach JavaScript.
-
Dostępność hooków:
- W komponentach klasowych nie są dostępne mechanizmy hooków, dlatego zwykłe funkcje mogą być tam użyte zamiast custom hooków.
- W zwykłych funkcjach nie można uzyskać dostępu do
useState
,useEffect
,useContext
itp. – w przeciwieństwie do custom hooków, które umożliwiają wykorzystanie tych mechanizmów w kontekście React.
Dodatkowe zasoby:
- Dokumentacja React – Custom Hooks
- Artykuł: „Building Custom Hooks in React” (dostępny na popularnych blogach technologicznych)
Które metody cyklu życia komponentu klasowego są zastępowane przez useEffect
w komponentach funkcyjnych?
Hook useEffect
w komponentach funkcyjnych zastępuje następujące metody cyklu życia:
-
componentDidMount – odpowiada za wykonanie efektu jednokrotnie po zamontowaniu komponentu.
Przykład:
useEffect(() => {
console.log("To jest odpowiednik componentDidMount w useEffect");
}, []); // pusta tablica oznacza wywołanie efektu tylko przy montowaniu
-
componentDidUpdate – odpowiada za wykonywanie efektu po każdej zmianie określonych zależności.
Przykład:
useEffect(() => {
console.log("Właściwość props.name uległa zmianie!");
}, [props.name]);
-
componentWillUnmount – umożliwia wykonywanie czynności przed odmontowaniem komponentu, poprzez zwrócenie funkcji porządkowej (cleanup function).
Przykład:
useEffect(() => {
console.log('Uruchomienie efektu');
return () => {
console.log('Komponent zostanie odmontowany');
};
});
Dodatkowe lektury:
- React – Managing Side Effects with useEffect
- Blog: „Understanding useEffect in React” – artykuł omawiający szczegółowo zachowanie useEffect
Czy Hooki zastępują render props oraz higher-order components (HOC)?
Często komponenty wykorzystujące render props oraz higher-order components renderują tylko jeden element. Zespół Reacta uważa, że Hooki stanowią prostsze rozwiązanie dla tego przypadku użycia.
Oba podejścia nadal mają swoje zastosowanie – na przykład, komponent odpowiadający za wirtualne przewijanie (virtual scroller) może korzystać z prop-u renderItem, lub komponent wizualnego kontenera może posiadać własną strukturę DOM. Jednakże, w większości scenariuszy Hooki wystarczą, a korzystanie z nich może również pomóc w ograniczeniu głębokości zagnieżdżeń w drzewie komponentów.
Przydatne materiały:
• Oficjalna dokumentacja React Hooks
• Render props – dokumentacja React
• Higher-Order Components – dokumentacja React
Jak zaktualizować stan w zagnieżdżonym obiekcie przy użyciu useState()?
Rozważmy komponent, który otrzymuje właściwość (prop) o następującej strukturze:
const styles = {
font: {
size: {
value: '22',
unit: 'px'
},
weight: 'bold',
color: '#663300',
family: 'arial',
align: 'center'
}
};
Jak zaktualizować tylko właściwość align
?
Aby zaktualizować jedynie pojedynczą właściwość w zagnieżdżonym obiekcie, należy użyć operatora spread. Dodatkowo, przy
aktualizacji stanu na podstawie poprzedniego stanu, warto zastosować wzorzec callback w funkcji setState
:
const {...styling} = styles;
const [style, setStyle] = useState(styling);
setStyle(prevStyle => ({
...prevStyle,
font: {...prevStyle.font, align: event.target.value}
}));
Przydatne materiały:
• Dokumentacja useState – React
• Operator spread w JavaScript – MDN
Jak ograniczyć wielokrotne renderowanie komponentu przy użyciu wielu wywołań useState?
Rozważmy poniższy przykładowy fragment kodu:
const getData = url => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(async () => {
const test = await api.get('/people');
if (test.ok) {
setLoading(false);
setData(test.data.results);
}
}, []);
return {data, loading};
};
W przypadku pobrania danych oraz aktualizacji dwóch oddzielnych zmiennych stanu (data
i flagi loading
), komponent (
np. tabela danych) jest renderowany dwukrotnie – mimo że obie aktualizacje stanu występują wewnątrz tej samej funkcji.
Istnieje kilka sposobów na ograniczenie tej sytuacji:
-
Scal stan
loading
idata
w jeden obiekt stanu:
Dzięki temu możliwe jest wykonanie pojedynczego wywołania funkcjisetState
, co spowoduje tylko jeden cykl renderowania.
const [userRequest, setUserRequest] = useState({
loading: false,
user: null,
});
useEffect(() => {
// Uwaga: wywołanie poniżej zastępuje cały obiekt, więc klucz user zostanie usunięty!
setUserRequest({loading: true});
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
setUserRequest({
loading: false,
user: data.results[0],
});
});
}, []);
-
Grupowanie aktualizacji stanu w React przed wersją 18:
Przed React 18, grupowanie (batching) aktualizacji stanu odbywa się tylko wtedy, gdy są one wywoływane z wewnątrz event handlerów React, takich jak kliknięcie przycisku lub zmiana wartości input. Aktualizacje wywołane poza takim handlerem, np. asynchroniczne wywołania, nie są automatycznie grupowane. -
Automatyczne grupowanie aktualizacji od React 18:
Od wersji React 18 (funkcja dostępna na zasadzie opt-in) wszystkie aktualizacje stanu są automatycznie grupowane, niezależnie od miejsca ich wywołania.
Przydatne materiały:
• Nowości w React 18 – blog Reacta
• Wprowadzenie do Hooków i useState – dokumentacja React
Podaj dobry przykład użycia hooka useCallback
w React
Wyobraź sobie, że masz komponent renderujący dużą listę elementów. Aby zapobiec niepotrzebnym renderowaniom listy,
owijamy go funkcją React.memo()
.
import useSearch from './fetch-items';
function MyBigList({term, onItemClick}) {
const items = useSearch(term);
const map = item => <div onClick={onItemClick}>{item}</div>;
return <div>{items.map(map)}</div>;
}
export default React.memo(MyBigList);
Komponent nadrzędny względem MyBigList
przekazuje funkcję obsługi zdarzeń, aby wiedzieć, kiedy element został
kliknięty:
import {useCallback} from 'react';
export function MyParent({term}) {
const onItemClick = useCallback(event => {
console.log('Kliknięto ', event.currentTarget);
}, [term]);
return (
<MyBigList
term={term}
onItemClick={onItemClick}
/>
);
}
Funkcja onItemClick
jest memoizowana za pomocą hooka useCallback()
. Dopóki wartość term
się nie zmienia,
useCallback()
zwraca ten sam obiekt funkcji, który jest przekazywany do MyBigList
jako właściwość (prop). W
momencie, gdy komponent MyParent
ponownie renderuje się, obiekt funkcji onItemClick
pozostaje niezmieniony i *
nie narusza memoizacji* komponentu MyBigList
. Jest to prawidłowy przypadek zastosowania hooka useCallback
.
Więcej informacji możesz znaleźć w oficjalnej dokumentacji React dotyczącej hooków.
Jaka jest różnica między useCallback
a useMemo
w praktyce?
Przy użyciu useCallback
memoizujesz funkcje, natomiast useMemo
służy do memoizowania dowolnej obliczonej
wartości.
const fn = () => 42; // zakładamy kosztowne obliczenia
const memoFn = useCallback(fn, [dep]); // (1)
const memoFnReturn = useMemo(fn, [dep]); // (2)
W przypadku (1)
otrzymujemy memoizowaną wersję funkcji fn
– ta sama referencja zostanie zachowana przy kolejnych
renderowaniach, o ile zależność dep
nie ulegnie zmianie. Jednak za każdym razem, gdy wywołujesz memoFn
, zaczyna
się od nowa kosztowne obliczenie.
Natomiast (2)
powoduje, że funkcja fn
zostanie wywołana tylko gdy zmieni się dep
, a jej zwrócona wartość (tutaj
42
) zostanie zapamiętana i przechowywana w zmiennej memoFnReturn
.
Dodatkowe informacje na temat obu hooków możesz znaleźć w artykule na blogu o optymalizacji Reacta.
Kiedy warto użyć flushSync
w ReactJS?
Wersja React 18 wprowadziła usprawnienia wydajnościowe, dzięki czemu automatycznie grupowane są (batching) wielokrotne aktualizacje stanu podczas pojedynczego renderowania komponentu.
- Aby wyłączyć automatyczny batching, możesz użyć
flushSync
, co spowoduje renderowanie komponentu po każdej aktualizacji stanu. Może to być potrzebne, gdy jakiś fragment kodu wymaga natychmiastowego odczytu elementów z DOM po zmianie stanu.
Przykład przy automatycznym batchingu:
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React wykona tylko jedno renderowanie na końcu (to właśnie batching!)
}
Przykład z użyciem flushSync
:
import {flushSync} from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// DOM został już zaktualizowany
flushSync(() => {
setFlag(f => !f);
});
// DOM został już zaktualizowany
}
Szczegóły dotyczące flushSync
są opisane w dokumentacji Reacta.
Czy niestandardowy hook Reacta może zwracać JSX?
Chociaż nie ma sztywnej reguły, która określałaby, jak definiować niestandardowe hooki i jaka logika powinna się w nich znaleźć, zwracanie JSX z hooka jest antywzorcem. Istnieje kilka negatywnych aspektów takiego rozwiązania:
- Gdy tworzysz hook, który zwraca komponent JSX, de facto definiujesz ten komponent wewnątrz funkcjonalnego komponentu. W rezultacie przy każdym renderowaniu tworzona jest nowa instancja komponentu. Może to powodować wielokrotne odmontowywanie i ponowne montowanie komponentu, co negatywnie wpływa na wydajność. Dodatkowo, jeśli komponent posiada własny stan, jego stan zostanie zresetowany przy każdym renderowaniu komponentu nadrzędnego.
- Definiując komponent JSX wewnątrz hooka, pozbawiasz siebie możliwości zastosowania mechanizmu lazy loadingu (leniwego ładowania) dla tego komponentu.
- Jakakolwiek optymalizacja wydajnościowa komponentu wymaga użycia hooka
useMemo
, który nie daje takiej elastyczności jak możliwość porównywania właściwości wReact.memo()
.
Zaletą tego rozwiązania mogłaby być możliwość kontrolowania stanu komponentu ze strony komponentu nadrzędnego. Jednak analogiczną logikę można zaimplementować, stosując podejście sterowanego komponentu (controlled component).
Dodatkowe informacje oraz przykłady dotyczące tworzenia niestandardowych hooków znajdziesz w artykule na FullStack.Cafe oraz w książce "The Road to React".
Różnica pomiędzy użyciem useMemo a kombinacją useEffect + useState
Rozważmy dwa podejścia:
Podejście 1 – useEffect + useState
import { expensiveCalculation } from "foo";
function useCalculate(someNumber: number): number {
const [result, setResult] = useState<number>(null);
useEffect(() => {
setResult(expensiveCalculation(someNumber));
}, [someNumber]);
return result;
}
W tym rozwiązaniu – przy każdej zmianie zależności (w tym przypadku someNumber) – hook useEffect powoduje wywołanie funkcji, która aktualizuje stan za pomocą setResult. To z kolei skutkuje podwójnym renderowaniem komponentu: pierwsze renderowanie odbywa się z przestarzałymi danymi (lub wartością początkową), a następnie po zaktualizowaniu stanu następuje kolejny render.
Podejście 2 – useMemo
import { expensiveCalculation } from "foo";
function useCalculateWithMemo(someNumber: number): number {
return useMemo(() => {
return expensiveCalculation(someNumber);
}, [someNumber]);
}
Wersja z useMemo oblicza wartość podczas renderowania i zwraca wynik bez konieczności kolejnego renderu. Efektem jest natychmiastowe uzyskanie prawidłowej wartości, co eliminuje opóźnienie wynikające z asynchronicznego ustawiania stanu.
Podsumowanie
- Wydajność: Chociaż częstotliwość wywołań funkcji expensiveCalculation jest identyczna w obu podejściach, podejście z useEffect + useState skutkuje dodatkowymi renderami – najpierw z wartością początkową (np. null), a następnie z nowo obliczoną wartością. To może negatywnie wpływać na wydajność, szczególnie przy intensywnych obliczeniach.
- Czytelność: Użycie useMemo sprawia, że kod jest bardziej przejrzysty i zwięzły, eliminując zbędne operacje oraz dodatkowe renderowanie.
Polecane materiały:
Jak zoptymalizować kod z wykorzystaniem React Hooks
Rozważmy poniższy kod, w którym mamy do czynienia z listą użytkowników (wyobraźmy sobie, że jest ich 100000):
import React from 'react';
const users = [
{ id: 'a', name: 'Robin' },
{ id: 'b', name: 'Dennis' },
// ...
];
const App = () => {
const [text, setText] = React.useState('');
const [search, setSearch] = React.useState('');
const handleText = (event) => {
setText(event.target.value);
};
const handleSearch = () => {
setSearch(text);
};
const filteredUsers = users.filter((user) => {
return user.name.toLowerCase().includes(search.toLowerCase());
});
return (
<div>
<input type="text" value={text} onChange={handleText} />
<button type="button" onClick={handleSearch}>
Search
</button>
<List list={filteredUsers} />
</div>
);
};
const List = ({ list }) => {
return (
<ul>
{list.map((item) => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
};
const ListItem = ({ item }) => {
return <li>{item.name}</li>;
};
export default App;
W tym przypadku, mimo że przetwarzanie listy użytkowników (filteredUsers
) odbywa się tylko po kliknięciu przycisku (zmiana stanu search), funkcja filtra jest wykonywana przy każdym naciśnięciu klawisza, gdy użytkownik wpisuje tekst. Nawet jeżeli lista nie zmienia się dynamicznie podczas wpisywania, wywołanie funkcji filtra dla każdej ostatniej wartości inputu może generować zbędne obciążenie – szczególnie przy bardzo dużej liczbie elementów.
Proponowana optymalizacja
Wykorzystując hooka useMemo, możemy zmemoizować wynik filtrowania, tak aby funkcja filtrowania była wywoływana tylko wtedy, gdy zależność (w tym przypadku search) ulegnie zmianie:
function App() {
const [text, setText] = React.useState('');
const [search, setSearch] = React.useState('');
const handleText = (event) => {
setText(event.target.value);
};
const handleSearch = () => {
setSearch(text);
};
const filteredUsers = React.useMemo(() =>
users.filter((user) => {
console.log('Filter function is running ...');
return user.name.toLowerCase().includes(search.toLowerCase());
}), [search]
);
return (
<div>
<input type="text" value={text} onChange={handleText} />
<button type="button" onClick={handleSearch}>
Search
</button>
<List list={filteredUsers} />
</div>
);
}
Dzięki zastosowaniu useMemo funkcja filtrująca jest wykonywana tylko wtedy, gdy zmienia się stan search – co w rezultacie zmniejsza liczbę obliczeń podczas wpisywania tekstu. Jest to szczególnie ważne, gdy mamy do czynienia z bardzo dużymi zbiorami danych.
Polecane materiały: