Einführung in die Solid JavaScript Bibliothek

Avatar of Charlie Gerard
Charlie Gerard am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Solid ist eine reaktive JavaScript-Bibliothek zur Erstellung von Benutzeroberflächen ohne virtuelles DOM. Es kompiliert Vorlagen einmal zu echten DOM-Knoten und verpackt Aktualisierungen in feingranulare Reaktionen, sodass bei Zustandsaktualisierungen nur der zugehörige Code ausgeführt wird.

Auf diese Weise kann der Compiler das anfängliche Rendering optimieren und die Laufzeit die Updates. Dieser Fokus auf Leistung macht es zu einem der am besten bewerteten JavaScript-Frameworks.

Ich wurde neugierig darauf und wollte es ausprobieren, also habe ich einige Zeit damit verbracht, eine kleine To-Do-App zu erstellen, um zu untersuchen, wie dieses Framework Komponenten rendert, den Zustand aktualisiert, Stores einrichtet und vieles mehr.

Hier ist die endgültige Demo, wenn Sie es einfach nicht erwarten können, den endgültigen Code und das Ergebnis zu sehen

Erste Schritte

Wie die meisten Frameworks können wir mit der Installation des npm-Pakets beginnen. Um das Framework mit JSX zu verwenden, führen Sie Folgendes aus:

npm install solid-js babel-preset-solid

Anschließend müssen wir babel-preset-solid zu unserer Babel-, Webpack- oder Rollup-Konfigurationsdatei hinzufügen mit

"presets": ["solid"]

Oder wenn Sie eine kleine App aufbauen möchten, können Sie auch eine ihrer Vorlagen verwenden

# Create a small app from a Solid template
npx degit solidjs/templates/js my-app
 
# Change directory to the project created
cd my-app
 
# Install dependencies
npm i # or yarn or pnpm
 
# Start the dev server
npm run dev

Es gibt TypeScript-Unterstützung. Wenn Sie also ein TypeScript-Projekt starten möchten, ändern Sie den ersten Befehl in npx degit solidjs/templates/ts my-app.

Erstellen und Rendern von Komponenten

Zum Rendern von Komponenten ist die Syntax ähnlich wie bei React.js, sodass sie Ihnen vertraut vorkommen mag

import { render } from "solid-js/web";
 
const HelloMessage = props => <div>Hello {props.name}</div>;
 
render(
 () => <HelloMessage name="Taylor" />,
 document.getElementById("hello-example")
);

Wir müssen damit beginnen, die Funktion render zu importieren, dann erstellen wir ein Div mit etwas Text und einer Prop, und wir rufen render auf und übergeben die Komponente und das Container-Element.

Dieser Code wird dann zu echten DOM-Ausdrücken kompiliert. Zum Beispiel sieht der obige Code-Schnipsel, sobald er von Solid kompiliert wurde, ungefähr so aus

import { render, template, insert, createComponent } from "solid-js/web";
 
const _tmpl$ = template(`<div>Hello </div>`);
 
const HelloMessage = props => {
 const _el$ = _tmpl$.cloneNode(true);
 insert(_el$, () => props.name);
 return _el$;
};
 
render(
 () => createComponent(HelloMessage, { name: "Taylor" }),
 document.getElementById("hello-example")
);

Der Solid Playground ist ziemlich cool und zeigt, dass Solid verschiedene Möglichkeiten zum Rendern bietet, darunter Client-seitig, Server-seitig und Client-seitig mit Hydration.

Ändernde Werte mit Signalen verfolgen

Solid verwendet einen Hook namens createSignal, der zwei Funktionen zurückgibt: einen Getter und einen Setter. Wenn Sie es gewohnt sind, ein Framework wie React.js zu verwenden, mag Ihnen das etwas seltsam vorkommen. Normalerweise würden Sie erwarten, dass das erste Element der Wert selbst ist; in Solid müssen wir jedoch explizit den Getter aufrufen, um abzufangen, wo der Wert gelesen wird, um seine Änderungen zu verfolgen.

Wenn wir zum Beispiel den folgenden Code schreiben

const [todos, addTodos] = createSignal([]);

Das Protokollieren von todos gibt nicht den Wert zurück, sondern eine Funktion. Wenn wir den Wert verwenden möchten, müssen wir die Funktion aufrufen, wie in todos().

Für eine kleine To-Do-Liste wäre dies

import { createSignal } from "solid-js";
 
const TodoList = () => {
 let input;
 const [todos, addTodos] = createSignal([]);
 
 const addTodo = value => {
   return addTodos([...todos(), value]);
 };
 
 return (
   <section>
     <h1>To do list:</h1>
     <label for="todo-item">Todo item</label>
     <input type="text" ref={input} name="todo-item" id="todo-item" />
     <button onClick={() => addTodo(input.value)}>Add item</button>
     <ul>
       {todos().map(item => (
         <li>{item}</li>
       ))}
     </ul>
   </section>
 );
};

Der obige Code-Schnipsel würde ein Textfeld anzeigen und nach dem Klicken auf die Schaltfläche „Element hinzufügen“ die Todos mit dem neuen Element aktualisieren und es in einer Liste anzeigen.

Das kann sehr ähnlich wie die Verwendung von useState erscheinen, wie unterscheidet sich die Verwendung eines Getters? Betrachten Sie den folgenden Code-Schnipsel

console.log("Create Signals");
const [firstName, setFirstName] = createSignal("Whitney");
const [lastName, setLastName] = createSignal("Houston");
const [displayFullName, setDisplayFullName] = createSignal(true);
 
const displayName = createMemo(() => {
 if (!displayFullName()) return firstName();
 return `${firstName()} ${lastName()}`;
});
 
createEffect(() => console.log("My name is", displayName()));
 
console.log("Set showFullName: false ");
setDisplayFullName(false);
 
console.log("Change lastName ");
setLastName("Boop");
 
console.log("Set showFullName: true ");
setDisplayFullName(true);

Das Ausführen des obigen Codes würde zu folgendem Ergebnis führen:

Create Signals
 
My name is Whitney Houston
 
Set showFullName: false
 
My name is Whitney
 
Change lastName
 
Set showFullName: true
 
My name is Whitney Boop

Das Wichtigste, was Sie bemerken sollten, ist, dass My name is ... nach dem Festlegen eines neuen Nachnamens nicht protokolliert wird. Dies liegt daran, dass zu diesem Zeitpunkt niemand auf Änderungen an lastName() hört. Der neue Wert von displayName() wird erst gesetzt, wenn sich der Wert von displayFullName() ändert. Deshalb sehen wir den neuen Nachnamen, wenn setShowFullName wieder auf true gesetzt wird.

Dies gibt uns eine sicherere Möglichkeit, Wertaktualisierungen zu verfolgen.

Reaktivitätsprimitiven

Im letzten Code-Schnipsel habe ich createSignal eingeführt, aber auch ein paar andere Primitiven: createEffect und createMemo.

Lifecycle-Methoden

Solid stellt einige Lifecycle-Methoden zur Verfügung, wie z. B. onMount, onCleanup und onError. Wenn wir möchten, dass Code nach dem anfänglichen Rendering ausgeführt wird, müssen wir onMount verwenden

// Don't forget to import it first with 'import { onMount } from "solid-js";'
 
onMount(() => {
 console.log("I mounted!");
});

onCleanup ähnelt componentDidUnmount in React – es wird ausgeführt, wenn eine Neuberechnung des reaktiven Geltungsbereichs erfolgt.

onError wird ausgeführt, wenn ein Fehler im nächstgelegenen Gültigkeitsbereich eines Kindes auftritt. Wir könnten es zum Beispiel beim Abrufen von Daten verwenden, wenn dies fehlschlägt.

Stores

Um Stores für Daten zu erstellen, stellt Solid createStore zur Verfügung, dessen Rückgabewert ein schreibgeschütztes Proxy-Objekt und eine Setter-Funktion sind.

Wenn wir zum Beispiel unser Todo-Beispiel ändern würden, um einen Store anstelle von Zustand zu verwenden, würde es etwa so aussehen

const [todos, addTodos] = createStore({ list: [] });
 
createEffect(() => {
 console.log(todos.list);
});
 
onMount(() => {
 addTodos("list", [
   ...todos.list,
   { item: "a new todo item", completed: false }
 ]);
});

Der obige Code-Schnipsel würde zunächst ein Proxy-Objekt mit einem leeren Array protokollieren, gefolgt von einem Proxy-Objekt mit einem Array, das das Objekt {item: "a new todo item", completed: false} enthält.

Eine Sache, die man beachten sollte, ist, dass das Top-Level-State-Objekt nicht verfolgt werden kann, ohne auf eine Eigenschaft darin zuzugreifen – deshalb protokollieren wir todos.list und nicht todos.

Wenn wir in createEffect nur todo protokollieren würden, würden wir den anfänglichen Wert der Liste sehen, aber nicht den nach der Aktualisierung, die in onMount vorgenommen wurde.

Um Werte in Stores zu ändern, können wir sie mit der Setter-Funktion aktualisieren, die wir beim Verwenden von createStore definieren. Wenn wir zum Beispiel einen Todo-Listeneintrag zu „abgeschlossen“ aktualisieren möchten, könnten wir den Store auf diese Weise aktualisieren

const [todos, setTodos] = createStore({
 list: [{ item: "new item", completed: false }]
});
 
const markAsComplete = text => {
 setTodos(
   "list",
   i => i.item === text,
   "completed",
   c => !c
 );
};
 
return (
 <button onClick={() => markAsComplete("new item")}>Mark as complete</button>
);

Kontrollfluss

Um die Verschwendung durch die Neuerstellung aller DOM-Knoten bei jeder Aktualisierung mit Methoden wie .map() zu vermeiden, bietet Solid Vorlagenhelfer.

Einige davon sind verfügbar, wie z. B. For zum Durchlaufen von Elementen, Show zum bedingten Ein- und Ausblenden von Elementen, Switch und Match zum Anzeigen von Elementen, die einer bestimmten Bedingung entsprechen, und mehr!

Hier sind einige Beispiele, wie man sie verwendet

<For each={todos.list} fallback={<div>Loading...</div>}>
 {(item) => <div>{item}</div>}
</For>
 
<Show when={todos.list[0].completed} fallback={<div>Loading...</div>}>
 <div>1st item completed</div>
</Show>
 
<Switch fallback={<div>No items</div>}>
 <Match when={todos.list[0].completed}>
   <CompletedList />
 </Match>
 <Match when={!todos.list[0].completed}>
   <TodosList />
 </Match>
</Switch>

Demo-Projekt

Dies war eine kurze Einführung in die Grundlagen von Solid. Wenn Sie damit experimentieren möchten, habe ich ein Starter-Projekt erstellt, das Sie automatisch auf Netlify bereitstellen und auf Ihr GitHub klonen können, indem Sie auf die Schaltfläche unten klicken!

Dieses Projekt enthält die Standardkonfiguration für ein Solid-Projekt sowie eine Beispiel-Todo-App mit den grundlegenden Konzepten, die ich in diesem Beitrag erwähnt habe, um Ihnen den Einstieg zu erleichtern!

Dieses Framework hat noch viel mehr zu bieten, als ich hier behandelt habe. Schauen Sie sich also gerne die Dokumentation für weitere Informationen an!