Feingranulare Reaktivität typsicher nutzen
SolidJS verzichtet auf einen virtuellen DOM und aktualisiert stattdessen einzelne DOM-Knoten direkt über ein feingranulares Signal-System. TypeScript begleitet dieses Modell mit präzisen Typen für Signal-Zugriff, Komponenten-Props und typisierten Kontrollfluss-Komponenten.
Inhaltsverzeichnis
- 1. Feingranulare Reaktivität statt Re-Render der Komponente
- 2. createSignal: Getter und Setter als typisiertes Tupel
- 3. createEffect und createMemo: abgeleitete und reaktive Berechnungen
- 4. Komponenten und Props typsicher deklarieren
- 5. Typisierte Kontrollfluss-Komponenten: For, Show, Switch
- 6. createResource: asynchrone Daten typsicher laden
- 7. Stores: verschachtelte reaktive Objekte typsicher aktualisieren
- 8. SolidJS im Vergleich zum React-Rendermodell
- 9. Typische TypeScript-Fallstricke in SolidJS-Code
- 10. Zusammenfassung
- 11. FAQ
1. Feingranulare Reaktivität statt Re-Render der Komponente
Der grundlegende Unterschied zu React liegt darin, dass eine SolidJS-Komponentenfunktion nur ein einziges Mal ausgeführt wird, um die Beschreibung des DOM-Baums zu erzeugen. Änderungen an reaktiven Werten lösen anschließend keinen erneuten Aufruf der Komponentenfunktion aus, sondern aktualisieren gezielt genau die DOM-Knoten oder Attribute, die tatsächlich von diesem Wert abhängen.
Diese Feinkörnigkeit hat direkte Konsequenzen für den TypeScript-Umgang: Eine Variable, die aus einem Signal gelesen wird, muss als Funktion aufgerufen werden, um ihren aktuellen Wert zu erhalten, statt wie in React einfach als Variable im JSX zu erscheinen. TypeScript unterscheidet dabei strikt zwischen dem Getter selbst und dem darin enthaltenen Wert.
Wer aus einem Rendermodell mit erneutem Funktionsaufruf kommt, muss dieses mentale Modell bewusst umstellen, denn Debugging-Strategien, die auf wiederholte Log-Ausgaben innerhalb der Komponentenfunktion setzen, funktionieren in SolidJS anders und erfordern stattdessen gezielte Log-Aufrufe innerhalb von createEffect. Auch Browser-Erweiterungen für Entwicklerwerkzeuge müssen dieses Modell unterstützen, um reaktive Abhängigkeiten sinnvoll sichtbar zu machen, statt nur Komponentenbäume wie bei React darzustellen.
2. createSignal: Getter und Setter als typisiertes Tupel
createSignal liefert ein Tupel aus einem parameterlosen Getter vom Typ Accessor und einem Setter vom Typ Setter zurück. Der generische Typparameter wird entweder aus dem Initialwert inferiert oder bei optionalen Werten explizit angegeben, etwa createSignal, damit der Setter später auch echte Nutzerobjekte akzeptiert.
Der Setter akzeptiert sowohl einen direkten neuen Wert als auch eine Updater-Funktion, die den vorherigen Wert entgegennimmt und den neuen zurückgibt, was insbesondere bei Objekt- und Array-Zuständen wichtig ist, um konsistente Aktualisierungen zu garantieren. TypeScript prüft beide Aufrufvarianten gegen denselben generischen Typ.
import { createSignal } from 'solid-js';
const [count, setCount] = createSignal(0);
const [user, setUser] = createSignal<User | null>(null);
setCount((prev) => prev + 1);
setUser({ id: '1', name: 'Entwicklerin' });
console.log(count()); // Getter aufrufen, um den Wert zu lesen
3. createEffect und createMemo: abgeleitete und reaktive Berechnungen
createEffect führt eine Funktion beim ersten Aufruf sofort aus und danach automatisch erneut, sobald sich einer der innerhalb gelesenen Signal-Werte ändert. SolidJS verfolgt diese Abhängigkeiten automatisch durch reines Beobachten, welche Getter innerhalb der Effektfunktion tatsächlich aufgerufen wurden, ganz ohne explizite Abhängigkeitsliste wie bei React-Hooks.
createMemo berechnet aus reaktiven Werten einen abgeleiteten, zwischengespeicherten Wert und gibt selbst wieder einen Accessor zurück. Der zurückgegebene Wert wird nur neu berechnet, wenn sich eine der gelesenen Abhängigkeiten tatsächlich ändert, was teure Berechnungen wie das Filtern großer Listen effizient hält.
import { createEffect, createMemo, createSignal } from 'solid-js';
const [items, setItems] = createSignal<string[]>(['a', 'bb', 'ccc']);
const [minLength, setMinLength] = createSignal(2);
const filtered = createMemo(() =>
items().filter((item) => item.length >= minLength()),
);
createEffect(() => {
console.log('Gefilterte Anzahl:', filtered().length);
});
4. Komponenten und Props typsicher deklarieren
Eine SolidJS-Komponente ist eine gewöhnliche Funktion, deren Props-Parameter über den Hilfstyp Component aus solid-js typisiert werden kann. Weil eine Komponentenfunktion nur einmal ausgeführt wird, dürfen Props niemals destrukturiert werden, da Destrukturierung den reaktiven Zugriff auf einzelne Felder zerstört und nur den Wert zum Zeitpunkt des Aufrufs einfriert.
TypeScript kann diesen Fallstrick nicht automatisch verhindern, weshalb Disziplin gefragt ist: Statt function Greeting({ name }: Props) muss function Greeting(props: Props) geschrieben und props.name direkt im JSX referenziert werden, damit spätere Änderungen an name tatsächlich reaktiv im DOM ankommen.
import type { Component } from 'solid-js';
type GreetingProps = {
name: string;
onGreet?: (name: string) => void;
};
export const Greeting: Component<GreetingProps> = (props) => {
return (
<button onClick={() => props.onGreet?.(props.name)}>
Hallo, {props.name}
</button>
);
};
5. Typisierte Kontrollfluss-Komponenten: For, Show, Switch
Weil JSX-Ausdrücke in SolidJS nur einmal ausgewertet werden, ersetzen dedizierte Komponenten wie For, Show und Switch/Match die aus React gewohnten JavaScript-Konstrukte wie array.map() oder ternäre Ausdrücke, um reaktives Verhalten korrekt abzubilden. Jede dieser Komponenten ist generisch typisiert und passt den Typ ihres Render-Callbacks an den Elementtyp der übergebenen Liste an.
Show arbeitet mit einem generischen when-Prop, dessen Wahrheitswert TypeScript im Callback für eine korrekte Typ-Verengung nutzt: Ist when vom Typ User | null, erhält der Kinder-Callback im positiven Fall den bereits auf User verengten Typ, ganz ohne zusätzliche manuelle Prüfung.
import { For, Show } from 'solid-js';
function UserList(props: { users: User[]; selected: User | null }) {
return (
<>
<Show when={props.selected} fallback={<p>Keine Auswahl</p>}>
{(selected) => <p>Ausgewählt: {selected().name}</p>}
</Show>
<For each={props.users}>
{(user) => <li>{user.name}</li>}
</For>
</>
);
}
6. createResource: asynchrone Daten typsicher laden
createResource kapselt das Laden asynchroner Daten und liefert einen Accessor zurück, der neben dem eigentlichen Wert zusätzliche reaktive Felder wie loading und error trägt. Der generische Typparameter der Fetcher-Funktion bestimmt automatisch den Typ des zurückgegebenen Werts, sodass data() entweder undefined während des Ladens oder den vollständig typisierten Wert nach erfolgreichem Laden liefert.
Wird der Fetcher mit einer reaktiven Quelle als erstem Argument kombiniert, etwa einer Nutzer-ID aus einem Signal, löst jede Änderung dieser Quelle automatisch einen neuen, korrekt typisierten Abruf aus, ohne dass ein manueller useEffect-Ersatz mit expliziter Abhängigkeitsliste geschrieben werden muss.
import { createResource, createSignal } from 'solid-js';
const [userId, setUserId] = createSignal('u1');
async function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
const [user] = createResource(userId, fetchUser);
// user() ist User | undefined, user.loading ist boolean
7. Stores: verschachtelte reaktive Objekte typsicher aktualisieren
Für komplexere, verschachtelte Zustände bietet createStore aus solid-js/store eine feingranular reaktive Alternative zu mehreren einzelnen Signalen. Anders als bei createSignal liefert der Store-Getter ein reaktives Proxy-Objekt, dessen einzelne Felder unabhängig voneinander reaktiv gelesen werden können, ohne dass ein Zugriff auf ein Feld die Reaktivität auf andere Felder ausweitet.
Aktualisierungen erfolgen über einen typisierten Setter, der sowohl Pfadangaben als auch Updater-Funktionen unterstützt, etwa setStore('user', 'name', 'Neuer Name'). TypeScript prüft dabei, dass der übergebene Pfad tatsächlich zur verschachtelten Struktur des Store-Typs passt, was bei tief verschachtelten Zuständen deutlich weniger fehleranfällig ist als manuelles Immutable-Update-Spreading.
import { createStore } from 'solid-js/store';
type AppState = {
user: { id: string; name: string };
settings: { darkMode: boolean };
};
const [store, setStore] = createStore<AppState>({
user: { id: '1', name: 'Entwicklerin' },
settings: { darkMode: false },
});
setStore('settings', 'darkMode', (dark) => !dark);
8. SolidJS im Vergleich zum React-Rendermodell
In React löst jede Zustandsänderung einen erneuten Aufruf der Komponentenfunktion aus, gefolgt von einem Abgleich über den virtuellen DOM, um herauszufinden, welche echten DOM-Knoten tatsächlich aktualisiert werden müssen. SolidJS überspringt diesen Vergleichsschritt vollständig, weil das Kompilierungssystem bereits zur Build-Zeit weiß, welcher DOM-Knoten von welchem Signal abhängt, und direkte Update-Funktionen dafür generiert.
Aus TypeScript-Sicht bedeutet das, dass Hooks-Regeln wie die feste Aufrufreihenfolge von useState in React für SolidJS irrelevant sind, da createSignal und Co. keine Hooks im React-Sinne sind, sondern gewöhnliche Funktionen, die auch in Bedingungen oder Schleifen aufgerufen werden dürfen, ohne Regelverstöße zu riskieren.
9. Typische TypeScript-Fallstricke in SolidJS-Code
Der häufigste Fehler ist, ein Signal versehentlich als Wert statt als Funktion zu verwenden, etwa if (count > 5) statt if (count() > 5). TypeScript erkennt diesen Fehler zuverlässig, weil ein Vergleich zwischen einer Funktion und einer Zahl einen Typfehler erzeugt, was in der Praxis viele dieser Fehler bereits beim Kompilieren abfängt, bevor sie zur Laufzeit als fehlendes Reaktivitäts-Tracking auffallen.
Ein subtilerer Fallstrick betrifft asynchrone Callbacks innerhalb von createEffect: Wird ein Signal-Getter erst nach einem await aufgerufen, erkennt SolidJS diese Abhängigkeit nicht mehr, weil das automatische Dependency-Tracking nur synchron während des ersten Laufs der Effektfunktion funktioniert. TypeScript warnt hier nicht automatisch, weshalb Signal-Zugriffe bewusst vor dem ersten await platziert werden sollten.
| Merkmal | SolidJS | React | Vorteil |
|---|---|---|---|
| Update-Mechanismus | Direkte, feingranulare DOM-Updates | Re-Render mit virtuellem DOM-Vergleich | Kein Diffing-Overhead bei SolidJS |
| Signal-Zugriff | count() als Funktionsaufruf | count als Variable | TypeScript erzwingt korrekten Zugriff |
| Props-Handling | Niemals destrukturieren | Destrukturierung üblich | Erhält Reaktivität in SolidJS |
| Abhängigkeitsliste | Automatisch getrackt | Manuell in useEffect-Array | Weniger Fehlerquellen bei SolidJS |
Mironsoft
TypeScript-Migration, Typsicherheit und Team-Onboarding
JavaScript-Codebasis ohne Typsicherheit, aber keine Zeit für eine Rundum-Migration?
Wir migrieren bestehende JavaScript-Projekte schrittweise zu TypeScript, richten strikte Compiler-Einstellungen sauber ein und bringen Teams mit Code-Reviews und Style-Guides auf denselben Typsicherheits-Stand.
Migrations-Fahrplan
Schrittweise JS-zu-TS-Migration ohne Big-Bang-Risiko planen und umsetzen.
Strict-Mode-Einführung
tsconfig.json, ESLint-Regeln und CI-Checks für dauerhafte Typsicherheit aufsetzen.
Team-Onboarding
Entwickler mit Workshops und Code-Reviews in TypeScript-Best-Practices einarbeiten.
10. Zusammenfassung
TypeScript mit SolidJS
Signal-Typ
createSignal
Komponenten
Component
Kontrollfluss
For, Show, Switch ersetzen map() und ternäre Ausdrücke
Async-Daten
createResource liefert typisierten Wert plus loading/error