Wenn sich Komponenten-Props bzw State ändern oder die Komponente zum ersten Mal eingebunden wird:
"Main Effect": Komponente wir mit aktuellen Daten (neu) gerendert
mögliche "Side Effects": Auslösen von API-Abfragen, Speichern von Daten, Explizite Änderungen am Dokument, Starten von Timern, ...
typische Fälle von Side Effects:
manche Side Effects müssen später "aufgeräumt" werden:
Implementierung:
in Funktionskomponenten: mittels des Effect Hooks
in Klassenkomponenten: mit Hilfe von Lifecycle-Methoden
Drei wichtige Methoden zum Abfragen von Ereignissen im Lebenszyklus einer Komponente:
componentDidMount
componentDidUpdate
componentWillUnmount
componentDidUpdate
bekommt die vorhergehenden Props als Parameter übergeben:
class MyComponent extends Component {
// ...
componentDidUpdate(prevProps, prevState) {
if (this.state.foo !== prevState.foo) {
doSomething();
}
}
}
Beispiel: Komponente, die Wechselkurse zwischen zwei Währungen lädt:
class ExchangeRate extends Component {
// ...
componentDidMount() {
this.loadExchangeRate();
}
componentDidUpdate(prevProps, prevState) {
if (
this.state.from !== prevState.from ||
this.state.to !== prevState.to
) {
this.loadExchangeRate();
}
}
}
Erweitertes Beispiel: Abbrechen veralteter API-Anfragen
class ExchangeRate extends Component {
// ...
componentDidMount() {
this.loadExchangeRate();
}
componentDidUpdate(prevProps, prevState) {
if (
this.state.from !== prevState.from ||
this.state.to !== prevState.to
) {
this.cancelPreviousQuery();
this.loadExchangeRate();
}
}
componentWillUnmount() {
this.cancelPreviousQuery();
}
}
Der Effect Hook kann verwendet werden, um Aktionen zu setzen, wenn eine Komponente zum ersten Mal eingebunden wird, oder wenn sich deren Props / State geändert haben
useEffect(
effect, // what should happen
dependencies // array of values to watch
);
Die Effekt-Funktion nach dem (Re-)Rendering einer Komponente ausgeführt, falls sich eine der Abhängigkeiten geändert hat
Beispiel: Laden von Umrechnungskursen, wenn die Komponente zum ersten Mal eingebunden wird oder wenn sich eine Währung ändert:
const [from, setFrom] = useState('USD');
const [to, setTo] = useState('EUR');
const [rate, setRate] = useState<number | null>(null);
async function loadRate(from: number, to: number) {
// ...
}
useEffect(() => {
loadRate(from, to);
}, [from, to]);
Beispiel: Laden von Todos, wenn die Komponente zum ersten Mal eingebunden wird:
const [todos, setTodos] = useState([]);
async function loadTodos() {
// ...
}
useEffect(() => {
loadTodos();
}, []);
Wenn kein zweiter Parameter übergeben wird, wird die Funktion nach jedem Rendering ausgeführt; dies kann eventuelle Probleme mit veralteten Daten verhindern
useEffect(() => {
ensureExchangeRateIsLoaded();
});
Eine Effect-Funktion kann eine "Cleanup-Funktion" zurückgeben
Diese Funktion wird ausgeführt, bevor der Effect zum nächsten Mal läuft oder bevor die Komponente entfernt wird
Beispiel: Komponente, die veraltete Queries abbricht:
function ExchangeRate() {
// ...
useEffect(() => {
async function loadExchangeRate() {
// ... (initiate a new query)
}
function cancel() {
// ... (cancel this query)
}
loadExchangeRate();
return cancel;
}, [from, to]);
// ...
}
Eine Effect-Funktion darf keine asynchrone Funktion sein
Hintergrund:
ein eventueller Rückgabewert einer Effect-Funktion wird immer als Cleanup-Funktion interpretiert
asynchrone Funktionen geben immer Promises zurück
ungültig:
useEffect(loadSearchResultsAsync, [query]);
gültig:
useEffect(() => {
loadSearchResultsAsync();
}, [query]);
Beispiel: Laden von Wechselkursen von einem API, wenn sich die ausgewählten Währungen ändern:
function ExchangeRate() {
const [from, setFrom] = useState('USD');
const [to, setTo] = useState('EUR');
const [rate, setRate] = useState(null);
async function loadRate(from: string, to: string) {
setRate(null);
const rate = await fetchExchangeRate(from, to);
setRate(rate);
}
useEffect(() => {
loadRate(from, to);
}, [from, to]);
// render two dropdowns for selecting currencies
// and show the exchange rate
}
Funktion, die Daten lädt:
async function fetchExchangeRate(
from: string,
to: string
): Promise<number> {
const res = await fetch(
`https://api.exchangerate.host/convert?from=${from}&to=${to}`
);
const data = await res.json();
return data.result;
}
vollständiger Code (Klassenkomponenten und Funktionskomponenten, inklusive "cleanup"):
Beispiele für abfragbare APIs:
Beispiele und Ãœbungen, die nicht mit APIs interagieren
function Clock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const intervalId = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(intervalId);
}, []);
return <div>{time.toLocaleTimeString()}</div>;
}
Counter, der seinen Wert in localStorage speichert, wenn sich dieser ändert:
function PersistentCounter() {
const [count, setCount] = useState(null);
function loadCount() {
const lsCount = localStorage.getItem('count');
if (lsCount !== null) {
setCount(Number(lsCount));
} else {
setCount(0);
}
}
function saveCount() {
if (count !== null) {
localStorage.setItem('count', count);
}
}
useEffect(loadCount, []);
useEffect(saveCount, [count]);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
Ãœbung: Speichere den state einer der vorigen Anwendungen, (z.B. slideshow) in localStorage
Wir erstellen eine Komponente, die den Dokumenttitel dynamisch setzen kann:
<DocumentTitle value="my custom title" />
Diese Komponente kann irgendwo in der React-Anwendung vorkommen.
const DocumentTitle = (props) => {
useEffect(() => {
document.title = props.value;
}, [props.value]);
return null;
};
Benutzer wird nach 10 Sekunden Inaktivität automatisch ausgeloggt:
const App = () => {
const [loggedIn, setLoggedIn] = useState(true);
const [lastInteraction, setLastInteraction] = useState(
new Date()
);
// restart the logout timer on user interaction
useEffect(() => {
const logout = () => setLoggedIn(false);
const timeoutId = setTimeout(logout, 10000);
return () => clearTimeout(timeoutId);
}, [lastInteraction]);
return (
<button onClick={() => setLastInteraction(new Date())}>
{loggedIn
? 'click to stay logged in'
: 'logged out automatically'}
</button>
);
};