Solid.js – ein Tutorial
Solid hinterlässt einen soliden Eindruck.baranq | shutterstock.com
Solid.js verfolgt einen einzigartigen Ansatz, wenn es um Reactive State Management geht. Das hat auch andere reaktive JavaScript-Frameworks maßgeblich beeinflusst. Solid bietet einen kompakten Satz reaktiver Primitive, die auf “Signals” statt auf State basieren. Diese unterstützen Funktionen höherer Ordnung. Dank seines übersichtlichen Kerndesigns, das auf Geschwindigkeit ausgelegt ist und seinen Enterprise-Funktionen brachte es das Framework im Rahmen der aktuellen “State of JavaScript“-Umfrage auf einen beeindruckenden Wert von 90 Prozent bei der Entwicklerzufriedenheit.
Sämtliche Features von Solid schnell zu durchdringen, kann allerdings überwältigend sein. Die gute Nachricht: Sie können darauf vertrauen, dass Solid seinem Namen gerecht wird und mit robusten Funktionalitäten auch das Gros Ihrer Bedürfnisse abdeckt.
Neues Projekt mit SolidStart aufbauen
Wie andere Frameworks seiner Klasse bietet Solid eine Full-Stack-Plattform, die Funktionen wie serverseitiges Rendering (SSR) unterstützt. Das JavaScript-Framework verfügt zudem über ein Befehlszeilen-Tool, um Projekte zu starten und zu verwalten. SolidStart ist dabei die offizielle Methode, um ein neues Solid-Projekt aufzubauen.
Nachdem ich kürzlich erste Erfahrungen mit Roo Code und Gemini in Visual Studio Code gesammelt habe, kommt auch für dieses Tutorial dieser KI-gestützte Stack zum Einsatz. Der Vergleichbarkeit halber, habe ich jedoch auch manuell programmiert. Auf meinen Request hin hat Roo Code zunächst folgendes Gerüst für unser Solid-Projekt generiert:
npm create solid@latest -- --template typescript --name iw-solid-app
Obwohl dieser Befehl sinnvoll ist, um ein neues Projekt mit einem Template und TypeScript zu starten, war die KI leider nicht intelligent genug, um mit dem interaktiven CLI-Prompt zu interagieren. Nachdem ich einige Fragen der KI beantwortet hatte, resultierte der Befehl dann in einer Fehlermeldung. Also führte ich das Kommando aus, auf das ich normalerweise zurückgegriffen hätte: npm create solid@latest. Diese Mischung aus manueller und KI-unterstützter Programmierarbeit scheint der aktuelle Stand der Technik für moderne Entwicklungsprojekte zu sein. Es ist also weiterhin notwendig, tatsächlich zu verstehen, wie die Technologie funktioniert.
Um die App im Developer-Modus auszuführen, nutzen Sie: $ npm run dev. Wenn Sie nun http://localhost:3000 aufrufen, sehen Sie folgendes Template:
Matthew Tyson
Der Dev-Server unterstützt On-the-Fly-Updates einschließlich Hot Module Replacement (HMR). Wenn Sie also Änderungen an Dateien vornehmen, werden diese automatisch im Browser aktualisiert.
Reactive State Management mit Signals
Wie andere Frameworks auch, ermöglicht Solid es, Komponenten zu erstellen, um Funktionen im User Interface zu kapseln. Und wie React nutzt das Framework JSX als Template-Sprache. Die counter.tsx-Komponente von SolidStart vermittelt Ihnen eine Vorstellung davon, wie Signals in Solid genutzt werden, um den State zu managen:
import { createSignal } from "solid-js";
import "./Counter.css";
export default function Counter() {
const [count, setCount] = createSignal(0);
return (
setCount(count() + 1)} type="button">
Clicks: {count()}
);
}
Das deckt sich weitgehend mit einer React-Komponente – mit dem Unterschied, dass React useState verwendet und nicht wie Solid createSignal. Signals funktionieren etwas anders als States: Sie ermöglichen dem Framework granularen Zugriff auf das Document Object Model (DOM). Die Engine kann dadurch jeweils nur den spezifischen Knoten aktualisieren, bei dem das nötig ist. Signals sind zudem auch allgemeinere Operatoren in Solid. Im Gegensatz dazu managt useState lediglich den Komponenten-State.
Zu beachten ist darüber hinaus, dass über den Call der Getter-Funktion auf Signals (wie count) zugegriffen wird – nicht direkt über die Variable.
Signals & Effects
Weil Solid-Komponenten ein Funktionsaufruf sind, werden sie nur einmal ausgeführt, sobald sie erstellt werden. Sollten Sie auf ein Signal außerhalb des Templates zugreifen wollen, müssen Sie es in einen Effect “wrappen”. Innerhalb von JSX können Sie dazu einfach den Signal-Getter aufrufen, so wie wir es gerade mit count() getan haben. Sobald sich der reaktive Wert ändert, erhalten Sie ihn. Im Hauptteil der Funktion müssen Sie jedoch einen Effect einsetzen:
console.log("Count:",count()); // ❌ not tracked - only runs once during initialization.
createEffect(()=>{ console.log(count()); // ✅ will update whenever `count()` changes.
});
// snippet from the docs
Hierbei stellt useEffect eine Art Ad-hoc-Beobachter für Signals dar. Diese Funktion zu verwenden, empfiehlt sich immer dann, wenn Sie einen Effect außerhalb der Vorlage basierend auf einem Signal ausführen müssen. Mit createSignal, createEffect und der nativen Reaktivität von JSX stehen Ihnen die meisten grundlegenden Reactive-Elemente zur Verfügung.
Remote API abrufen
Auf den Grundfunktionen von Signals fußen weitere Solid-Funktionen. Eine davon ist createResource. Mit dieser simplen Erweiterung von createSignal lassen sich asynchrone Requests auf reaktive Weise einfach handhaben. Wir verwenden diese Funktion, um eine “Witz-Komponente” zu erstellen, die zehn Witze aus der Joke-API abruft und anzeigt. Zunächst fügen wir dazu die Komponente in index.tsx ein:
import Joker from "~/components/Joker";
// …
Anschließend können wir im Komponentenverzeichnis eine simple Fetch-Operation erstellen:
import { createResource } from "solid-js";
const fetchProgrammingJokes = async () => {
const response = await fetch(`https://official-joke-api.appspot.com/jokes/programming/ten`);
return response.json();
};
export default function JokerSimple() {
const [jokes] = createResource(fetchProgrammingJokes);
return (
Raw Jokes JSON
{JSON.stringify(jokes(), null, 2)}
);
}
Diese Version weist weder Error Handling noch Display-Logik auf. Vielmehr besteht der Sinn darin, hervorzuheben, wie createResource die createSignal-Funktion umschließt und asynchrone Vorgänge wie fetch deutlich erleichtert. Der Knackpunkt liegt dabei darin, dass fetchProgrammingJokes ein Promise zurückgibt und der Call von createResource es ermöglicht, eine jokes()-Funktion zu erstellen, die sich frei im Template verwenden lässt, um die Ergebnisse zu erfassen.
Idiomatische Loops in Solid
Mit dem – Element gestalten wir die Anzeige nun etwas gefälliger. Dabei handelt es sich um einen idiomatischen Loop, der eine bessere Performance gewährleisten soll als der funktionale map-Operator:
import { createResource, For } from "solid-js";
//...
{(joke) => (
{joke.setup}
{joke.punchline}
)}
ermöglicht, ein iterierbares Objekt – wie das Array, das vom Jokes()-Promise zurückgegeben wird – zu verarbeiten, wenn es aufgelöst wird. Um die Asynchronous-Resolution-Mechanismen müssen Sie sich nicht kümmern. Innerhalb des Loops haben wir zudem Zugriff auf das Iteratorobjekt joke. Dieses nutzen wir an dieser Stelle, um eine einfache Liste von Elementen, respektive Witzen, zu erstellen. Ein (mehr oder weniger witziges) Beispiel: Wohin geht eine API zum Essen? Ins RESTaurant.
Suspense Boundaries
Die -Komponente erleichtert es, Grenzen (Boundaries) zu definieren, an denen ein asynchroner Ladevorgang seine verschiedenen States anzeigen kann. In der folgenden Beispielanwendung (die auf unserem vorherigen Beispiel aufbaut), sehen Sie in Aktion:
import { createResource, For, Suspense } from "solid-js";
// ...
Fetching jokes... please wait!}>
{(joke) => (
{joke.setup}
{joke.punchline}
)}
Zu beachten ist hier, dass Sie nicht mitteilen müssen, auf welches Promise oder welche Ressource es “wartet”. Es erkennt automatisch die asynchronen Vorgänge in seinem Inneren und wartet darauf, dass diese aufgelöst werden. In der Zwischenzeit werden Platzhalter anzeigt. Das gestaltet es sehr einfach, die Fetch States zu händeln.
Error Boundaries
Davon abgesehen ist es häufig nötig, für Benutzeroberflächen auch Boundaries für das Error Handling zu definieren. Auch diesen Prozess gestaltet Solid schmerzfrei:
import { createResource, For, Suspense, ErrorBoundary } from "solid-js";
(
Failed to load jokes!
Error details: {err.message}
)}
>
Fetching punchlines... please wait!}>
{(joke) => (
{joke.setup}
{joke.punchline}
)}
Dieser Code entspricht unserer vorherigen Version – nur, dass er nun von der ErrorBoundary-Komponente umschlossen wird. Mit dieser können Sie einfach einen Teil der Benutzeroberfläche definieren, der angezeigt werden soll, wenn beim asynchronen Call etwas schiefläuft. Um das zu testen, können Sie einen Tippfehler in die Jokes-API einbauen.
Event Handling in Solid
Das Event Handling von Solid testen wir nun, indem wir die Pointe aus dem Inline Display entfernen, einen Mausklick auf das Setup erfassen und dann die Pointe in einer Warnmeldung anzeigen:
handleSetupClick(joke.punchline)}
style={{ cursor: 'pointer', color: 'blue', textDecoration: 'underline' }}
title="Click to reveal punchline"
>
Setup: {joke.setup}
Hier ist der Ereignis-Handler, der innerhalb der Komponentenfunktion definiert ist:
export default function JokerSimple() {
const handleSetupClick = (punchline: string) => {
alert(punchline);
};
//...
Wenn Sie nun auf die Setup-Zeile klicken, bewirkt die onClick-Eigenschaft, dass handleSetupClick ausgeführt wird. Diese ist als Template Expression (in geschweiften Klammern) und als anonyme Inline-Funktion definiert, die den Call an die simple handleSetupClick-Funktion weiterleitet. Diese öffnet wiederum einen Alert mit der Pointe. Letztgenannte wird aus dem Template übergeben, indem joke.punchline als Argument aufgerufen wird. Wenn Sie auf "Wo hängen Programmierer gerne rum?" klicken, erhalten Sie die Antwort: "In der Foo Bar." (dieser Witz ist so schlecht, dass er schon wieder gut ist).
Eine reaktive Checkbox
Stellen Sie sich nun vor, Sie möchten die Möglichkeit hinzufügen, zwischen der Anzeige von Programmiererwitzen und allgemeinen Witzen umzuschalten. Die Remote-API überprüft dazu, ob "programming" im vorletzten Part der URL (…/jokes/programming/ten versus …/jokes/ten) vorhanden ist oder nicht. Um den Benutzern zu ermöglichen, die Anzeige umzuschalten, fügen wir nun auf unserer Seite ein Kontrollkästchen hinzu. Dazu erstellen wir zuerst ein neues Signal namens jokeType – mit einem leeren String als Anfangswert:
const [jokeType, setJokeType] = createSignal("");
Im nächsten Schritt fügen wir nun ein Checkbox-Element am Anfang des Haupt-Divs ein:
{setJokeType(jokeType()==''?'programming/':'')}}>
Die Attribute checked und onInput sind Solid-spezifisch. Um den Wert des jokeType()-Signal mit "programming/" zu vergleichen, nutzt das checked-Attribut einen Token,. Mit anderen Worten: Das Kästchen ist angehakt, wenn der Wert von jokeType „programming/“ ist. In diesem Beispiel nutzen wir einen String für das Kontrollkästchen anstelle eines Booleschen Werts, weil createResource keine Reactive Update für falsche Werte auslöst. Das onInput-Attribut verarbeitet den Input Event für die Checkbox. Wird es ausgelöst, ändern wir den Wert von jokeType so, dass er zwischen einem leeren String und "programming/" wechselt. Diesen sich verändernden Wert verwenden wir in der Joke-Fetcher-URL.
Signal & Ressource kombinieren
Im letzten Schritt kombinieren wir nun das neue Signal und die Ressource. Hierbei handelt es sich um eine Form der Derived Reactivity, bei der die Ressource den State des Signals überwacht und sich entsprechend aktualisiert. Anschließend werden die Teile der App, die die Ressource überwachen, nacheinander aktualisiert. Zusätzlich zu dem Promise, das den Fetching-Job übernimmt, akzeptiert createResource ein Quellsignal als erstes Argument. Das erleichtert es, sie für unseren Use Case miteinander zu verketten:
const [jokes] = createResource(jokeType, fetchJokes);
Wenn sich nun der Wert von jokeType ändert, aktualisiert die jokes()-Ressource auch die Elemente, die von ihr abhängig sind. Die Joke-Fetcher-Funktion erhält ebenfalls das Ergebnis des Quellsignals:
const fetchJokes = async (jokeType) => {
return (await fetch(`https://official-joke-api.appspot.com/jokes/${jokeType}ten`)).json();
}
Zu beachten ist dabei, dass das Signal jokeType eine direkte Variable im Argument von fetchJokes ist (das Ergebnis der Auflösung des jokeType-Promise). Die fetch-URL nutzt den Wert von jokeType. Wenn das Signal über das Kontrollkästchen geändert wird, erkennt Solid das und ruft die Liste der Witze automatisch erneut ab – mit der aktualisierten URL. (fm)
Dieser Artikel ist im Original bei unserer Schwesterpublikation Infoworld.com erschienen.
Hier finden Sie den kompletten Artikel: