Abrufen von veralteten Daten mit React-Hooks: Ein Leitfaden

Veröffentlicht: 2022-03-11

Die Nutzung der Stale-while-Revalidate-HTTP Cache-Control Erweiterung ist eine beliebte Technik. Dabei werden zwischengespeicherte (veraltete) Assets verwendet, wenn sie im Cache gefunden werden, und der Cache wird dann erneut validiert und bei Bedarf mit einer neueren Version des Assets aktualisiert. Daher der Name stale-while-revalidate .

So funktioniert stale-while-revalidate “.

Wenn eine Anfrage zum ersten Mal gesendet wird, wird sie vom Browser zwischengespeichert. Wenn dann dieselbe Anfrage ein zweites Mal gesendet wird, wird zuerst der Cache überprüft. Wenn der Cache dieser Anfrage verfügbar und gültig ist, wird der Cache als Antwort zurückgegeben. Dann wird der Cache auf Veraltung überprüft und aktualisiert, wenn er als veraltet befunden wird. Die Veraltung eines Caches wird durch den im Cache-Control Header vorhandenen max-age Wert zusammen mit stale-while-revalidate bestimmt.

Ein Flussdiagramm, das die Logik zum Veralten während der Neuvalidierung verfolgt. Es beginnt mit einer Anfrage. Wenn es nicht zwischengespeichert ist oder der Cache ungültig ist, wird die Anforderung gesendet, die Antwort zurückgegeben und der Cache aktualisiert. Andernfalls wird die zwischengespeicherte Antwort zurückgegeben, wonach der Cache auf Veraltung überprüft wird. Wenn es veraltet ist, wird eine Anfrage gesendet und der Cache aktualisiert.

Dies ermöglicht ein schnelles Laden von Seiten, da sich zwischengespeicherte Assets nicht mehr im kritischen Pfad befinden. Sie werden sofort geladen. Da Entwickler außerdem steuern, wie oft der Cache verwendet und aktualisiert wird, können sie verhindern, dass Browser Benutzern übermäßig veraltete Daten anzeigen.

Leser könnten denken, dass, wenn sie den Server bestimmte Header in seinen Antworten verwenden und den Browser von dort übernehmen lassen können, wozu dann die Verwendung von React und Hooks für das Caching erforderlich ist?

Es stellt sich heraus, dass der Server-und-Browser-Ansatz nur dann gut funktioniert, wenn wir statische Inhalte zwischenspeichern möchten. Was ist mit der Verwendung von „ stale-while-revalidate “ für eine dynamische API? In diesem Fall ist es schwierig, gute Werte für max-age und stale-while-revalidate zu finden. Häufig ist es die beste Option, den Cache zu entwerten und jedes Mal, wenn eine Anfrage gesendet wird, eine neue Antwort abzurufen. Dies bedeutet effektiv überhaupt kein Caching. Aber mit React und Hooks können wir es besser machen.

stale-while-revalidate für die API

Wir haben festgestellt, dass HTTPs „ stale-while-revalidate “ nicht gut mit dynamischen Anforderungen wie API-Aufrufen funktioniert.

Selbst wenn wir es letztendlich verwenden, gibt der Browser entweder den Cache oder die neue Antwort zurück, nicht beides. Dies passt nicht gut zu einer API-Anfrage, da wir jedes Mal, wenn eine Anfrage gesendet wird, neue Antworten möchten. Das Warten auf neue Antworten verzögert jedoch die sinnvolle Nutzbarkeit der App.

Also, was machen wir?

Wir implementieren einen benutzerdefinierten Caching-Mechanismus. Darin finden wir eine Möglichkeit, sowohl den Cache als auch die neue Antwort zurückzugeben. In der Benutzeroberfläche wird die zwischengespeicherte Antwort durch eine neue Antwort ersetzt, wenn sie verfügbar ist. So würde die Logik aussehen:

  1. Wenn eine Anfrage zum ersten Mal an den API-Server-Endpunkt gesendet wird, cachen Sie die Antwort und geben Sie sie dann zurück.
  2. Wenn dieselbe API-Anforderung das nächste Mal erfolgt, verwenden Sie sofort die zwischengespeicherte Antwort.
  3. Senden Sie dann die Anforderung asynchron, um eine neue Antwort abzurufen. Wenn die Antwort eintrifft, geben Sie Änderungen asynchron an die Benutzeroberfläche weiter und aktualisieren Sie den Cache.

Dieser Ansatz ermöglicht sofortige UI-Updates – da jede API-Anfrage zwischengespeichert wird – aber auch eventuelle Korrektheit in der UI, da neue Antwortdaten angezeigt werden, sobald sie verfügbar sind.

In diesem Tutorial sehen wir einen schrittweisen Ansatz zur Implementierung. Wir nennen diesen Ansatz „ stale-while-refresh “, da die Benutzeroberfläche tatsächlich aktualisiert wird, wenn sie die neue Antwort erhält.

Vorbereitungen: Die API

Um dieses Tutorial zu starten, benötigen wir zunächst eine API, von der wir Daten abrufen. Glücklicherweise gibt es eine Menge Schein-API-Dienste. Für dieses Tutorial verwenden wir reqres.in.

Die Daten, die wir abrufen, sind eine Liste von Benutzern mit einem page . So sieht der Abrufcode aus:

 fetch("https://reqres.in/api/users?page=2") .then(res => res.json()) .then(json => { console.log(json); });

Das Ausführen dieses Codes gibt uns die folgende Ausgabe. Hier ist eine sich nicht wiederholende Version davon:

 { page: 2, per_page: 6, total: 12, total_pages: 2, data: [ { id: 7, email: "[email protected]", first_name: "Michael", last_name: "Lawson", avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/follettkyle/128.jpg" }, // 5 more items ] }

Sie können sehen, dass dies wie eine echte API ist. Wir haben Paginierung in der Antwort. Der page ist für den Seitenwechsel verantwortlich, und wir haben insgesamt zwei Seiten im Datensatz.

Verwenden der API in einer React-App

Sehen wir uns an, wie wir die API in einer React-App verwenden. Sobald wir wissen, wie es geht, werden wir den Caching-Teil herausfinden. Wir werden eine Klasse verwenden, um unsere Komponente zu erstellen. Hier ist der Code:

 import React from "react"; import PropTypes from "prop-types"; export default class Component extends React.Component { state = { users: [] }; componentDidMount() { this.load(); } load() { fetch(`https://reqres.in/api/users?page=${this.props.page}`) .then(res => res.json()) .then(json => { this.setState({ users: json.data }); }); } componentDidUpdate(prevProps) { if (prevProps.page !== this.props.page) { this.load(); } } render() { const users = this.state.users.map(user => ( <p key={user.id}> <img src={user.avatar} alt={user.first_name} style={{ height: 24, width: 24 }} /> {user.first_name} {user.last_name} </p> )); return <div>{users}</div>; } } Component.propTypes = { page: PropTypes.number.isRequired };

Beachten Sie, dass wir den page über props erhalten, wie es in realen Anwendungen häufig vorkommt. Außerdem haben wir eine Funktion „ componentDidUpdate “, die die API-Daten jedes Mal neu abruft, wenn sich this.props.page ändert.

An diesem Punkt wird eine Liste mit sechs Benutzern angezeigt, da die API sechs Elemente pro Seite zurückgibt:

Eine Vorschau unseres React-Komponenten-Prototyps: sechs zentrierte Linien, jede mit einem Foto links neben einem Namen.

Hinzufügen von Stale-while-refresh-Caching

Wenn wir das Caching für veraltete Aktualisierungen hinzufügen möchten, müssen wir unsere App-Logik wie folgt aktualisieren:

  1. Zwischenspeichern Sie die Antwort einer Anforderung eindeutig, nachdem sie zum ersten Mal abgerufen wurde.
  2. Geben Sie die zwischengespeicherte Antwort sofort zurück, wenn der Cache einer Anfrage gefunden wird. Senden Sie dann die Anfrage und geben Sie die neue Antwort asynchron zurück. Cachen Sie diese Antwort auch für das nächste Mal.

Wir können dies tun, indem wir ein globales CACHE Objekt haben, das den Cache eindeutig speichert. Aus Gründen der Eindeutigkeit können wir den Wert this.props.page als Schlüssel in unserem CACHE Objekt verwenden. Dann codieren wir einfach den oben erwähnten Algorithmus.

 import apiFetch from "./apiFetch"; const CACHE = {}; export default class Component extends React.Component { state = { users: [] }; componentDidMount() { this.load(); } load() { if (CACHE[this.props.page] !== undefined) { this.setState({ users: CACHE[this.props.page] }); } apiFetch(`https://reqres.in/api/users?page=${this.props.page}`).then( json => { CACHE[this.props.page] = json.data; this.setState({ users: json.data }); } ); } componentDidUpdate(prevProps) { if (prevProps.page !== this.props.page) { this.load(); } } render() { // same render code as above } }

Da der Cache zurückgegeben wird, sobald er gefunden wurde, und da die neuen Antwortdaten auch von setState zurückgegeben werden, bedeutet dies, dass wir ab der zweiten Anfrage nahtlose UI-Updates und keine Wartezeit mehr auf der App haben. Das ist perfekt, und es ist kurz gesagt die Stale-While-Refresh-Methode.

Ein Flussdiagramm, das die Logik für das Veralten während der Aktualisierung nachverfolgt. Es beginnt mit einer Anfrage. Wenn es zwischengespeichert ist, wird setState() mit der zwischengespeicherten Antwort aufgerufen. In jedem Fall wird die Anfrage gesendet, der Cache gesetzt und setState() mit einer neuen Antwort aufgerufen.

Die apiFetch Funktion hier ist nichts anderes als ein Wrapper über fetch , damit wir den Vorteil des Cachings in Echtzeit sehen können. Dies geschieht durch Hinzufügen eines zufälligen Benutzers zur Liste der users , die von der API-Anforderung zurückgegeben werden. Es fügt auch eine zufällige Verzögerung hinzu:

 export default async function apiFetch(...args) { await delay(Math.ceil(400 + Math.random() * 300)); const res = await fetch(...args); const json = await res.json(); json.data.push(getFakeUser()); return json; } function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

Die Funktion getFakeUser() ist hier für die Erstellung eines gefälschten Benutzerobjekts verantwortlich.

Mit diesen Änderungen ist unsere API realer als zuvor.

  1. Es hat eine zufällige Verzögerung beim Antworten.
  2. Es gibt leicht unterschiedliche Daten für dieselben Anforderungen zurück.

Wenn wir also die page ändern, die von unserer Hauptkomponente an die Component übergeben wird, können wir das API-Caching in Aktion sehen. Versuchen Sie, in dieser CodeSandbox alle paar Sekunden auf die Umschalttaste zu klicken, und Sie sollten ein Verhalten wie dieses sehen:

Eine Animation, die die Umschaltseite mit aktiviertem Caching zeigt. Die Besonderheiten sind im Artikel beschrieben.

Wenn Sie genau hinsehen, passieren einige Dinge.

  1. Wenn die App startet und sich im Standardzustand befindet, sehen wir eine Liste mit sieben Benutzern. Notieren Sie sich den letzten Benutzer in der Liste, da dieser Benutzer zufällig geändert wird, wenn diese Anforderung das nächste Mal gesendet wird.
  2. Wenn wir zum ersten Mal auf Toggle klicken, wartet es eine kurze Zeit (400-700 ms) und aktualisiert dann die Liste auf die nächste Seite.
  3. Jetzt sind wir auf der zweiten Seite. Beachten Sie erneut den letzten Benutzer in der Liste.
  4. Jetzt klicken wir erneut auf Toggle und die App kehrt zur ersten Seite zurück. Beachten Sie, dass der letzte Eintrag jetzt immer noch derselbe Benutzer ist, den wir in Schritt 1 notiert haben, und sich später in den neuen (zufälligen) Benutzer ändert. Dies liegt daran, dass zunächst der Cache angezeigt wurde und dann die eigentliche Antwort einsetzte.
  5. Wir klicken erneut auf Toggle. Das gleiche Phänomen tritt auf. Die zwischengespeicherte Antwort vom letzten Mal wird sofort geladen, und dann werden neue Daten abgerufen, und so sehen wir die letzte Eintragsaktualisierung von dem, was wir in Schritt 3 notiert haben.

Das ist es, das Caching, nach dem wir gesucht haben. Dieser Ansatz leidet jedoch unter einem Problem der Codeduplizierung. Mal sehen, wie es läuft, wenn wir eine andere Datenabrufkomponente mit Caching haben. Diese Komponente zeigt die Elemente anders als unsere erste Komponente.

Hinzufügen von Stale-while-refresh zu einer anderen Komponente

Wir können dies tun, indem wir einfach die Logik aus der ersten Komponente kopieren. Unsere zweite Komponente zeigt eine Liste von Katzen:

 const CACHE = {}; export default class Component2 extends React.Component { state = { cats: [] }; componentDidMount() { this.load(); } load() { if (CACHE[this.props.page] !== undefined) { this.setState({ cats: CACHE[this.props.page] }); } apiFetch(`https://reqres.in/api/cats?page=${this.props.page}`).then( json => { CACHE[this.props.page] = json.data; this.setState({ cats: json.data }); } ); } componentDidUpdate(prevProps) { if (prevProps.page !== this.props.page) { this.load(); } } render() { const cats = this.state.cats.map(cat => ( <p key={cat.id} style={{ background: cat.color, padding: "4px", width: 240 }} > {cat.name} (born {cat.year}) </p> )); return <div>{cats}</div>; } }

Wie Sie sehen können, ist die hier verwendete Komponentenlogik ziemlich identisch mit der ersten Komponente. Der einzige Unterschied besteht im angeforderten Endpunkt und darin, dass die Listenelemente anders angezeigt werden.

Nun zeigen wir diese beiden Komponenten nebeneinander. Sie können sehen, dass sie sich ähnlich verhalten:

Eine Animation, die das Umschalten mit zwei nebeneinander liegenden Komponenten zeigt.

Um dieses Ergebnis zu erzielen, mussten wir viel Code duplizieren. Wenn wir mehrere Komponenten wie diese hätten, würden wir zu viel Code duplizieren.

Um es auf nicht duplizierende Weise zu lösen, können wir eine Komponente höherer Ordnung haben, um Daten abzurufen und zwischenzuspeichern und sie als Requisiten weiterzugeben. Es ist nicht ideal, aber es wird funktionieren. Aber wenn wir mehrere Anfragen in einer einzigen Komponente ausführen müssten, würde es sehr schnell hässlich werden, mehrere Komponenten höherer Ordnung zu haben.

Dann haben wir das Render-Requisiten-Muster, was wahrscheinlich der beste Weg ist, dies in Klassenkomponenten zu tun. Es funktioniert perfekt, aber andererseits neigt es zur „Wrapper-Hölle“ und erfordert, dass wir manchmal den aktuellen Kontext binden. Dies ist keine großartige Entwicklererfahrung und kann zu Frustration und Fehlern führen.

Hier retten React Hooks den Tag. Sie ermöglichen es uns, Komponentenlogik in einem wiederverwendbaren Container zu verpacken, sodass wir sie an mehreren Stellen verwenden können. React Hooks wurden in React 16.8 eingeführt und funktionieren nur mit Funktionskomponenten. Bevor wir zur React-Cache-Steuerung kommen – insbesondere zum Caching von Inhalten mit Hooks – wollen wir zunächst sehen, wie wir das einfache Abrufen von Daten in Funktionskomponenten durchführen.

API-Datenabruf in Funktionskomponenten

Um API-Daten in Funktionskomponenten abzurufen, verwenden wir useState und useEffect Hooks.

useState ist analog zu state und setState der Klassenkomponenten. Wir verwenden diesen Hook, um atomare Zustandscontainer innerhalb einer Funktionskomponente zu haben.

useEffect ist ein Lebenszyklus-Hook, und Sie können es sich als eine Kombination aus „ componentDidMount “, „ componentDidUpdate “ und „ componentWillUnmount “ vorstellen. Der zweite an useEffect übergebene Parameter wird als Abhängigkeitsarray bezeichnet. Wenn sich das Abhängigkeitsarray ändert, wird der als erstes Argument an useEffect übergebene Rückruf erneut ausgeführt.

So verwenden wir diese Hooks, um das Abrufen von Daten zu implementieren:

 import React, { useState, useEffect } from "react"; export default function Component({ page }) { const [users, setUsers] = useState([]); useEffect(() => { fetch(`https://reqres.in/api/users?page=${page}`) .then(res => res.json()) .then(json => { setUsers(json.data); }); }, [page]); const usersDOM = users.map(user => ( <p key={user.id}> <img src={user.avatar} alt={user.first_name} style={{ height: 24, width: 24 }} /> {user.first_name} {user.last_name} </p> )); return <div>{usersDOM}</div>; }

Indem wir page als Abhängigkeit von useEffect , weisen wir React an, unseren useEffect-Callback jedes Mal auszuführen, wenn die page geändert wird. Dies ist genau wie componentDidUpdate . Außerdem wird useEffect immer beim ersten Mal ausgeführt, sodass es auch wie componentDidMount funktioniert.

Stale-while-refresh in Funktionskomponenten

Wir wissen, dass useEffect den Lebenszyklusmethoden von Komponenten ähnelt. Wir können also die an sie übergebene Callback-Funktion ändern, um das Cachen für veraltete Aktualisierungen zu erstellen, das wir in Klassenkomponenten hatten. Bis auf den Hook useEffect bleibt alles gleich.

 const CACHE = {}; export default function Component({ page }) { const [users, setUsers] = useState([]); useEffect(() => { if (CACHE[page] !== undefined) { setUsers(CACHE[page]); } apiFetch(`https://reqres.in/api/users?page=${page}`).then(json => { CACHE[page] = json.data; setUsers(json.data); }); }, [page]); // ... create usersDOM from users return <div>{usersDOM}</div>; }

Somit haben wir in einer Funktionskomponente ein Cachen mit veralteter Aktualisierung, das funktioniert.

Wir können dasselbe für die zweite Komponente tun, das heißt, sie in Funktion umwandeln und das Caching für veraltete Aktualisierungen implementieren. Das Ergebnis wird identisch mit dem sein, was wir im Unterricht hatten.

Aber das ist nicht besser als Klassenkomponenten, oder? Sehen wir uns also an, wie wir die Leistungsfähigkeit eines benutzerdefinierten Hooks nutzen können, um eine modulare Stale-While-Refresh-Logik zu erstellen, die wir für mehrere Komponenten verwenden können.

Ein benutzerdefinierter Stale-while-refresh-Hook

Lassen Sie uns zunächst die Logik eingrenzen, die wir in einen benutzerdefinierten Hook verschieben möchten. Wenn Sie sich den vorherigen Code ansehen, wissen Sie, dass es sich um den Teil useState und useEffect . Genauer gesagt ist dies die Logik, die wir modularisieren möchten.

 const [users, setUsers] = useState([]); useEffect(() => { if (CACHE[page] !== undefined) { setUsers(CACHE[page]); } apiFetch(`https://reqres.in/api/users?page=${page}`).then(json => { CACHE[page] = json.data; setUsers(json.data); }); }, [page]);

Da wir es generisch machen müssen, müssen wir die URL dynamisch machen. Also müssen wir url als Argument haben. Wir müssen auch die Caching-Logik aktualisieren, da mehrere Anfragen denselben page haben können. Wenn die page in die Endpunkt-URL eingefügt wird, ergibt sich glücklicherweise ein eindeutiger Wert für jede eindeutige Anfrage. Wir können also einfach die gesamte URL als Schlüssel für das Caching verwenden:

 const [data, setData] = useState([]); useEffect(() => { if (CACHE[url] !== undefined) { setData(CACHE[url]); } apiFetch(url).then(json => { CACHE[url] = json.data; setData(json.data); }); }, [url]);

Das wars so ziemlich. Nachdem wir es in eine Funktion verpackt haben, haben wir unseren benutzerdefinierten Hook. Schauen Sie unten nach.

 const CACHE = {}; export default function useStaleRefresh(url, defaultValue = []) { const [data, setData] = useState(defaultValue); useEffect(() => { // cacheID is how a cache is identified against a unique request const cacheID = url; // look in cache and set response if present if (CACHE[cacheID] !== undefined) { setData(CACHE[cacheID]); } // fetch new data apiFetch(url).then(newData => { CACHE[cacheID] = newData.data; setData(newData.data); }); }, [url]); return data; }

Beachten Sie, dass wir ein weiteres Argument namens defaultValue hinzugefügt haben. Der Standardwert eines API-Aufrufs kann unterschiedlich sein, wenn Sie diesen Hook in mehreren Komponenten verwenden. Deshalb haben wir es anpassbar gemacht.

Dasselbe kann für den data im newData Objekt durchgeführt werden. Wenn Ihr benutzerdefinierter Hook eine Vielzahl von Daten zurückgibt, möchten Sie möglicherweise nur newData und nicht newData.data und diese Traversierung auf der Komponentenseite behandeln.

Jetzt, da wir unseren benutzerdefinierten Hook haben, der die schwere Arbeit beim Zwischenspeichern von veralteten Daten übernimmt, stecken wir ihn wie folgt in unsere Komponenten. Beachten Sie die schiere Menge an Code, die wir reduzieren konnten. Unsere gesamte Komponente besteht jetzt nur noch aus drei Anweisungen. Das ist ein großer Gewinn.

 import useStaleRefresh from "./useStaleRefresh"; export default function Component({ page }) { const users = useStaleRefresh(`https://reqres.in/api/users?page=${page}`, []); const usersDOM = users.map(user => ( <p key={user.id}> <img src={user.avatar} alt={user.first_name} style={{ height: 24, width: 24 }} /> {user.first_name} {user.last_name} </p> )); return <div>{usersDOM}</div>; }

Dasselbe können wir für die zweite Komponente tun. Es wird so aussehen:

 export default function Component2({ page }) { const cats = useStaleRefresh(`https://reqres.in/api/cats?page=${page}`, []); // ... create catsDOM from cats return <div>{catsDOM}</div>; }

Es ist leicht zu sehen, wie viel Boilerplate-Code wir sparen können, wenn wir diesen Hook verwenden. Der Code sieht auch besser aus. Wenn Sie die gesamte App in Aktion sehen möchten, gehen Sie zu dieser CodeSandbox.

Hinzufügen eines Ladeindikators zu useStaleRefresh

Jetzt, da wir die Grundlagen auf den Punkt gebracht haben, können wir unserem benutzerdefinierten Hook weitere Funktionen hinzufügen. Zum Beispiel können wir einen isLoading -Wert in den Hook einfügen, der wahr ist, wenn eine eindeutige Anfrage gesendet wird und wir in der Zwischenzeit keinen Cache zum Anzeigen haben.

Wir tun dies, indem wir einen separaten Status für isLoading und ihn entsprechend dem Status des Hooks setzen. Das heißt, wenn keine zwischengespeicherten Webinhalte verfügbar sind, setzen wir es auf true , andernfalls setzen wir es auf false .

Hier ist der aktualisierte Hook:

 export default function useStaleRefresh(url, defaultValue = []) { const [data, setData] = useState(defaultValue); const [isLoading, setLoading] = useState(true); useEffect(() => { // cacheID is how a cache is identified against a unique request const cacheID = url; // look in cache and set response if present if (CACHE[cacheID] !== undefined) { setData(CACHE[cacheID]); setLoading(false); } else { // else make sure loading set to true setLoading(true); } // fetch new data apiFetch(url).then(newData => { CACHE[cacheID] = newData.data; setData(newData.data); setLoading(false); }); }, [url]); return [data, isLoading]; }

Wir können jetzt den neuen isLoading Wert in unseren Komponenten verwenden.

 export default function Component({ page }) { const [users, isLoading] = useStaleRefresh( `https://reqres.in/api/users?page=${page}`, [] ); if (isLoading) { return <div>Loading</div>; } // ... create usersDOM from users return <div>{usersDOM}</div>; }

Beachten Sie, dass Sie danach den Text „Loading“ sehen, wenn eine eindeutige Anfrage zum ersten Mal gesendet wird und kein Cache vorhanden ist.

Eine Animation, die die Komponente mit einer implementierten Ladeanzeige zeigt.

useStaleRefresh Support Beliebige async Funktion

Wir können unseren benutzerdefinierten Hook noch leistungsfähiger machen, indem wir dafür sorgen, dass er jede async Funktion unterstützt, anstatt nur GET -Netzwerkanforderungen. Die Grundidee dahinter bleibt gleich.

  1. Im Hook rufen Sie eine asynchrone Funktion auf, die nach einiger Zeit einen Wert zurückgibt.
  2. Jeder eindeutige Aufruf einer asynchronen Funktion wird ordnungsgemäß zwischengespeichert.

Eine einfache Verkettung von function.name und arguments funktioniert als Cache-Schlüssel für unseren Anwendungsfall. Damit sieht unser Hook folgendermaßen aus:

 import { useState, useEffect, useRef } from "react"; import isEqual from "lodash/isEqual"; const CACHE = {}; export default function useStaleRefresh(fn, args, defaultValue = []) { const prevArgs = useRef(null); const [data, setData] = useState(defaultValue); const [isLoading, setLoading] = useState(true); useEffect(() => { // args is an object so deep compare to rule out false changes if (isEqual(args, prevArgs.current)) { return; } // cacheID is how a cache is identified against a unique request const cacheID = hashArgs(fn.name, ...args); // look in cache and set response if present if (CACHE[cacheID] !== undefined) { setData(CACHE[cacheID]); setLoading(false); } else { // else make sure loading set to true setLoading(true); } // fetch new data fn(...args).then(newData => { CACHE[cacheID] = newData; setData(newData); setLoading(false); }); }, [args, fn]); useEffect(() => { prevArgs.current = args; }); return [data, isLoading]; } function hashArgs(...args) { return args.reduce((acc, arg) => stringify(arg) + ":" + acc, ""); } function stringify(val) { return typeof val === "object" ? JSON.stringify(val) : String(val); }

Wie Sie sehen können, verwenden wir eine Kombination aus Funktionsname und seinen stringifizierten Argumenten, um einen Funktionsaufruf eindeutig zu identifizieren und ihn somit zwischenzuspeichern. Dies funktioniert für unsere einfache App, aber dieser Algorithmus ist anfällig für Kollisionen und langsame Vergleiche. (Mit unserialisierbaren Argumenten funktioniert es überhaupt nicht.) Für reale Apps ist also ein richtiger Hash-Algorithmus besser geeignet.

Eine weitere Sache, die hier zu beachten ist, ist die Verwendung von useRef . useRef wird verwendet, um Daten über den gesamten Lebenszyklus der einschließenden Komponente hinweg beizubehalten. Da args ein Array ist – was in JavaScript ein Objekt ist – bewirkt jedes erneute Rendern der Komponente mithilfe des Hooks, dass sich der args -Referenzzeiger ändert. Aber args ist Teil der Abhängigkeitsliste in unserem ersten useEffect . Das Ändern von useEffect args wird, auch wenn sich nichts geändert hat. Um dem entgegenzuwirken, führen wir mit args einen tiefen Vergleich zwischen alten und aktuellen Argumenten durch und lassen den args -Callback nur ausführen, wenn sich die useEffect tatsächlich geändert haben.

Jetzt können wir diesen neuen useStaleRefresh Hook wie folgt verwenden. Beachten Sie hier die Änderung von defaultValue . Da es sich um einen Allzweck-Hook handelt, verlassen wir uns nicht darauf, dass unser Hook den data im Antwortobjekt zurückgibt.

 export default function Component({ page }) { const [users, isLoading] = useStaleRefresh( apiFetch, [`https://reqres.in/api/users?page=${page}`], { data: [] } ); if (isLoading) { return <div>Loading</div>; } const usersDOM = users.data.map(user => ( <p key={user.id}> <img src={user.avatar} alt={user.first_name} style={{ height: 24, width: 24 }} /> {user.first_name} {user.last_name} </p> )); return <div>{usersDOM}</div>; }

Den gesamten Code finden Sie in dieser CodeSandbox.

Lassen Sie Benutzer nicht warten: Verwenden Sie Cache-Inhalte effektiv mit Stale-while-refresh- und React-Hooks

Der Hook useStaleRefresh , den wir in diesem Artikel erstellt haben, ist ein Machbarkeitsnachweis, der zeigt, was mit React Hooks möglich ist. Versuchen Sie, mit dem Code zu spielen und sehen Sie, ob Sie ihn in Ihre Anwendung integrieren können.

Alternativ können Sie auch versuchen, „stale-while-refresh“ über eine beliebte, gut gepflegte Open-Source-Bibliothek wie swr oder „react-query“ zu nutzen. Beide sind leistungsstarke Bibliotheken und unterstützen eine Vielzahl von Funktionen, die bei API-Anforderungen helfen.

React Hooks sind ein Game-Changer. Sie ermöglichen es uns, Komponentenlogik elegant zu teilen. Dies war zuvor nicht möglich, da der Komponentenstatus, die Lebenszyklusmethoden und das Rendern alle in einer Einheit zusammengefasst waren: Klassenkomponenten. Jetzt können wir für alle unterschiedliche Module haben. Dies ist großartig für die Zusammensetzbarkeit und das Schreiben von besserem Code. Ich verwende Funktionskomponenten und Hooks für den gesamten neuen React-Code, den ich schreibe, und ich kann dies allen React-Entwicklern wärmstens empfehlen.

Verwandt: Erstellen von React-Apps mit Redux Toolkit und RTK-Abfrage