Serveraktionen entstanden als Idee, Client-Code zu reduzieren und die Interaktionen zu vereinfachen, die eine Kommunikation mit dem Server erfordern. Es ist eine hervorragende Lösung, die es Entwicklern ermöglicht, weniger Code zu schreiben. Allerdings sind mit der Implementierung in andere Frameworks einige Herausforderungen verbunden, die nicht übersehen werden sollten.
In diesem Artikel werden wir über diese Probleme sprechen und wie wir in Brisa eine Lösung gefunden haben.
Um zu verstehen, was Serveraktionen bieten, ist es hilfreich, einen Blick darauf zu werfen, wie die Kommunikation mit dem Server früher war. Sie sind es wahrscheinlich gewohnt, bei jeder Interaktion mit dem Server die folgenden Aktionen auszuführen:
Diese sieben Aktionen werden bei jeder Interaktion wiederholt. Wenn Sie beispielsweise eine Seite mit 10 verschiedenen Interaktionen haben, wiederholen Sie einen sehr ähnlichen Code 10 Mal und ändern dabei nur Details wie die Art der Anfrage, die URL, die gesendeten Daten und den Status des Kunden.
Ein bekanntes Beispiel wäre
a:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
Und im Server:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
Erhöhung der Client-Bundle-Größe... und die Frustration der Entwickler.
Serveraktionen kapseln diese Aktionen in einem Remote Procedure Call (RPC), der die Client-Server-Kommunikation verwaltet, den Code auf dem Client reduziert und die Logik auf dem Server zentralisiert :
Hier erledigt der Brisa RPC alles für Sie.
Dies wäre der Code einer Serverkomponente:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
Hier schreiben Entwickler keinen Client-Code, da es sich um eine Serverkomponente handelt. Das onInput-Ereignis wird nach der Entprellung empfangen und vom Client-RPC verarbeitet, während der Server-RPC „Aktionssignale“ verwendet, um die Webkomponenten auszulösen, deren Signale bei dieser Store-Eigenschaft registriert sind.
Wie Sie sehen, wird dadurch der Servercode erheblich reduziert und das Beste ist, dass die Codegröße auf dem Client nicht mit jeder Interaktion zunimmt. Der RPC-Client-Code belegt eine feste Größe von 2 KB, unabhängig davon, ob Sie 10 oder 1000 solcher Interaktionen haben. Dies bedeutet, dass die Client-Bundle-Größe um 0 Bytes erhöht wird, mit anderen Worten: nicht erhöht wird.
Wenn außerdem ein erneutes Rendern erforderlich ist, erfolgt dies auf dem Server und wird im HTML-Streaming zurückgegeben, sodass der Benutzer die Änderungen viel früher sieht als bei der herkömmlichen Methode, bei der Sie diese Arbeit anschließend auf dem Client durchführen mussten die Serverantwort.
Auf diese Weise:
In anderen Frameworks wie React haben sie sich darauf konzentriert, dass Aktionen nur Teil des Formulars bei Submit sind, und nicht eines Ereignisses.
Dies ist ein Problem, da es viele Nicht-Formularereignisse gibt, die auch von einer Serverkomponente aus verarbeitet werden sollten, ohne dass Clientcode hinzugefügt werden muss. Zum Beispiel ein onInput einer Eingabe, um automatische Vorschläge zu machen, ein onScroll zum Laden eines unendlichen Scrollens, ein onMouseOver um einen Hover usw. auszuführen
Viele Frameworks haben die HTMX-Bibliothek auch als eine ganz andere Alternative zu Serveraktionen gesehen, obwohl sie tatsächlich sehr gute Ideen hervorgebracht hat, die mit Serveraktionen kombiniert werden können, um mehr Potenzial zu haben, indem einfach zusätzliche Attribute im HTML hinzugefügt werden, die die Der RPC-Client kann beispielsweise den DebounceInput berücksichtigen, den wir zuvor gesehen haben. Auch andere HTMX-Ideen wie der Indikator, der beim Senden der Anfrage einen Spinner anzeigt, oder die Möglichkeit, einen Fehler im RPC-Client zu behandeln.
Als Serveraktionen in React eingeführt wurden, kam es zu einem neuen Paradigmenwechsel, bei dem viele Entwickler den mentalen Chip ändern mussten, wenn sie mit ihnen arbeiteten.
Wir wollten es der Webplattform so vertraut wie möglich machen, damit Sie das serialisierte Ereignis vom Server erfassen und seine Eigenschaften verwenden können. Das einzige Ereignis, das sich etwas unterscheidet, ist onSubmit, das die FormData bereits übertragen hat und über die Eigenschaft e.formData verfügt. Die übrigen Ereigniseigenschaften sind jedoch interaktiv. Dies ist ein Beispiel für das Zurücksetzen eines Formulars:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
In diesem Beispiel gibt es überhaupt keinen Client-Code und während der Serveraktion können Sie die Schaltfläche „Senden“ deaktivieren mit dem Indikator mithilfe von CSS, sodass das Formular nicht zweimal gesendet werden kann, und zwar gleichzeitig Zur gleichen Zeit, nachdem Sie die Aktion auf dem Server ausgeführt und auf die Formulardaten zugegriffen haben mit e.formData und dann das Formular zurückgesetzt haben unter Verwendung derselben API des Ereignisses.
Mental ist es der Arbeit mit der Webplattform sehr ähnlich. Der einzige Unterschied besteht darin, dass alle Ereignisse aller Serverkomponenten Serveraktionen sind.
Auf diese Weise gibt es eine echte Interessenstrennung, bei der es NICHT notwendig ist, "Benutzerserver" oder "Benutzerserver" in Ihr Konto einzufügen Komponenten mehr.
Denken Sie daran, dass alles nur auf dem Server läuft. Die einzige Ausnahme gilt für den Ordner src/web-components, der auf dem Client ausgeführt wird und dort sind die Ereignisse normal.
In Brisa werden die Serveraktionen zwischen Serverkomponenten weitergegeben, als wären sie DOM-Ereignisse. Das heißt, von einer Serveraktion aus können Sie ein Ereignis einer Requisite einer Serverkomponente aufrufen und dann wird die Serveraktion der übergeordneten Serverkomponente ausgeführt usw.
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
In diesem Fall wird das Ereignis onAfterMyAction auf der übergeordneten Komponente ausgeführt und eine Aktion kann auf dem Server ausgeführt werden. Dies ist sehr nützlich, um Aktionen auf dem Server durchzuführen, die sich auf mehrere Serverkomponenten auswirken.
Besonders nach einigen Wochen waren Webkomponenten nach mehreren Diskussionen auf X (ehemals Twitter) etwas verpönt. Da es jedoch Teil des HTML ist, ist es aus mehreren Gründen die beste Möglichkeit, mit Serveraktionen zu interagieren:
Die Verwendung von Attributen in Web Components erfordert eine Serialisierung auf die gleiche Weise wie die Übertragung von Daten vom Server zum Client ohne Verwendung von Web Components. Daher ist bei der Verwendung beider keine zusätzliche Serialisierung zu verwalten.
Hinweis: Das Streamen von HTML und dessen Verarbeitung mit dem Diffing-Algorithmus habe ich bei Interesse in diesem anderen Artikel erklärt.
In Brisa haben wir ein neues Konzept hinzugefügt, um den Serveraktionen noch mehr Leistung zu verleihen. Dieses Konzept heißt „Aktionssignale“. Die Idee der „Aktionssignale“ besteht darin, dass Sie zwei Geschäfte haben, einen auf dem Server und einen auf dem Client.
Warum 2 Geschäfte?
Der standardmäßige Serverspeicher lebt nur auf der Anfrageebene. Und Sie können Daten weitergeben, die für den Kunden nicht sichtbar sind. Sie können beispielsweise festlegen, dass die Middleware den Benutzer festlegt und Zugriff auf vertrauliche Benutzerdaten in jeder Serverkomponente erhält. Durch das Leben auf Anfrageebene ist es unmöglich, Konflikte zwischen verschiedenen Anfragen zu haben, da jede Anfrage ihren eigenen Speicher hat und NICHT in einer Datenbank gespeichert ist, wenn die Wenn die Anfrage abgeschlossen ist, wird sie standardmäßig abgebrochen.
Andererseits handelt es sich im Kundenladen um einen Laden, bei dem jede Eigenschaft beim Konsum ein Signal ist, d. h. wenn Wenn es aktualisiert wird, reagiert die Webkomponente, die dieses Signal abgehört hat.
Das neue Konzept von „Action Signal“ besteht jedoch darin, dass wir die Lebensdauer des Serverspeichers über die Anforderung hinaus verlängern können. Dazu ist es notwendig, diesen Code zu verwenden:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
Diese transferToClient-Methode gibt Serverdaten an den Client-Speicher weiter und wandelt sie in Signale um. Auf diese Weise ist es in vielen Fällen nicht erforderlich, ein erneutes Rendern vom Server aus durchzuführen. Sie können einfach über eine Serveraktion die Signale der Webkomponenten reagieren lassen, die dieses Signal abgehört haben.
Diese Speicherübertragung verlängert die Lebensdauer des Serverspeichers jetzt:
Erste Serverkomponente rendern → Client → Serveraktion → Client → Serveraktion...
So geht es vom Leben nur auf Anforderungsebene zum dauerhaften Leben, kompatibel mit der Navigation zwischen Seiten.
Beispiel:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
In diesem Beispiel verlängern wir die Lebensdauer der Fehlerspeichereigenschaft, nicht um sie auf dem Client zu verwenden, sondern um sie in der Serveraktion und schließlich beim erneuten Rendern der Serveraktion wiederzuverwenden. Da es sich in diesem Fall um nicht sensible Daten handelt, ist eine Verschlüsselung nicht erforderlich. Dieser Beispielcode erfolgt vollständig auf dem Server, sogar das erneute Rendern, und der Benutzer wird die Fehler nach diesem Rendern auf dem Server sehen, wo der Server-RPC die HTML-Blöcke im Streaming sendet und der Client-RPC sie verarbeitet, um den Unterschied zu machen und anzuzeigen Fehler, um dem Benutzer Feedback zu geben.
Wenn innerhalb einer Serveraktion eine Variable verwendet wird, die auf Renderebene vorhanden war, verschlüsseln viele Frameworks wie Next.js 14 auf Sicherheitsebene diese Daten, um einen Snapshot der verwendeten Daten zu erstellen der Zeitpunkt der Darstellung. Das ist mehr oder weniger in Ordnung, aber Daten immer zu verschlüsseln ist mit Rechenaufwand verbunden und es handelt sich nicht immer um sensible Daten.
Um dieses Problem zu lösen, gibt es in Brisa verschiedene Anfragen, bei denen es beim ersten Rendern einen Wert hat und Sie in der Serveraktion den Wert erfassen können, den es in dieser Anfrage hat.
<input debounceInput={300} onInput={async (e) => { // All this code only runs on the server const data = await search(e.target.value); store.set("query", data); store.transferToClient(["query"]); }} />
Dies ist in einigen Fällen nützlich, aber nicht immer. Wenn Sie beispielsweise Math.random ausführen, wird es mit Sicherheit einen Unterschied zwischen dem ersten Rendering und der Ausführung der Serveraktion geben.
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
Aus diesem Grund haben wir das Konzept von „Aktionssignalen“ entwickelt, um Daten vom Serverspeicher zum Clientspeicher und der Entwickler kann nach Belieben entscheiden, ob er verschlüsselt es verschlüsselt oder nicht.
Anstatt die Datenbank über die Serveraktion abzufragen, möchten Sie manchmal Daten übertragen, die bereits im ersten Rendering vorhanden sind, auch wenn hierfür eine zugehörige Verschlüsselung erforderlich ist. Dazu verwenden Sie einfach:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
Wenn Sie das tun:
<input debounceInput={300} onInput={async (e) => { // All this code only runs on the server const data = await search(e.target.value); store.set("query", data); store.transferToClient(["query"]); }} />
Innerhalb einer Webkomponente (Client) wird immer verschlüsselt, aber auf dem Server wird es immer entschlüsselt.
Hinweis: Brisa verwendet aes-256-cbc zur Verschlüsselung, eine Kombination kryptografischer Algorithmen zur sicheren Verschlüsselung von Informationen, die von OpenSSL empfohlen werden. Verschlüsselungsschlüssel werden während der Erstellung Ihres Projekts generiert.
Obwohl wir in Brisa gerne das einfache Schreiben von Webkomponenten unterstützen, besteht das Ziel darin, eine SPA ohne Clientcode erstellen zu können und Webkomponenten nur dann zu verwenden, wenn es sich um eine reine Clientinteraktion handelt oder die Web-API berührt werden muss. Deshalb sind Serveraktionen so wichtig, da sie Interaktionen mit dem Server ermöglichen, ohne dass Client-Code geschrieben werden muss.
Wir empfehlen Ihnen, Brisa auszuprobieren. Sie müssen nur diesen Befehl im Terminal ausführen: bun create brisa, oder probieren Sie ein Beispiel aus, um zu sehen, wie es funktioniert.
Das obige ist der detaillierte Inhalt vonServeraktionen wurden behoben. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!