Ich arbeitete an einer großen React-Anwendung für ein Startup und wollte neben einigen guten Strategien zur Organisation unserer Stile auch dieses „Dark Mode“-Ding ausprobieren. Bei dem riesigen Ökosystem rund um React könnte man denken, dass es eine Standardlösung für Stilthemen gibt, aber eine kurze Websuche zeigt, dass dies wirklich nicht der Fall ist.
Es gibt viele verschiedene Optionen, aber viele davon knüpfen an sehr spezifische CSS-Strategien an, wie z. B. die Verwendung von CSS Modules, einer Form von CSS-in-JS usw. Ich habe auch Tools gefunden, die für bestimmte Frameworks wie Gatsby spezifisch sind, aber nicht für ein generisches React-Projekt. Was ich suchte, war ein grundlegendes System, das einfach einzurichten und zu verwenden ist, ohne viele Hürden überwinden zu müssen; etwas Schnelles, etwas Einfaches, mit dem sich ein ganzes Team von Front-End- und Full-Stack-Entwicklern schnell vertraut machen kann.
Die bestehende Lösung, die mir am besten gefiel, konzentrierte sich auf die Verwendung von CSS-Variablen und Datenattributen, gefunden in dieser StackOverflow-Antwort. Aber das stützte sich auch auf einige useRef-Sachen, die sich etwas umständlich anfühlten. Wie man in jeder Teleshopping-Sendung sagt: es muss einen besseren Weg geben!
Zum Glück gibt es den. Durch die Kombination dieser allgemeinen CSS-Variablenstrategie mit dem schönen useLocalStorage Hook haben wir ein leistungsstarkes, einfach zu bedienendes Theming-System. Ich werde Sie durch die Einrichtung und Ausführung führen, beginnend mit einer brandneuen React-App. Und wenn Sie bis zum Ende dabei bleiben, zeige ich Ihnen auch, wie Sie es mit react-scoped-css integrieren können, was dies zu meiner absolut bevorzugten Methode für die Arbeit mit CSS in React macht.
Projekteinrichtung
Nehmen wir das an ein sehr guter Ausgangspunkt: den Anfang.
Diese Anleitung setzt grundlegende Kenntnisse in CSS, JavaScript und React voraus.
Stellen Sie zuerst sicher, dass Sie eine aktuelle Version von Node und npm installiert haben. Navigieren Sie dann zu dem Ordner, in dem Ihr Projekt leben soll, führen Sie dort git bash aus (oder Ihr bevorzugtes Kommandozeilen-Tool) und führen Sie dann
npx create-react-app easy-react-themes --template typescript
Ersetzen Sie easy-react-themes durch den Namen Ihres Projekts und lassen Sie die Option --template typescript weg, wenn Sie lieber in JavaScript arbeiten möchten. Ich mag TypeScript, aber für diese Anleitung spielt es ehrlich gesagt keine Rolle, abgesehen davon, dass die Dateien mit .ts/.tsx statt mit .js/.jsx enden.
Öffnen wir nun unser brandneues Projekt in einem Code-Editor. Ich verwende VS Code für dieses Beispiel, und wenn Sie dasselbe tun, können Sie diese Befehle ausführen
cd easy-react-themes
code .

Wenn Sie als Nächstes npm start ausführen, wird Ihr Entwicklungsserver gestartet und dies in einem neuen Browserfenster angezeigt

Und installieren Sie schließlich noch das Paket use-local-storage mit
npm i use-local-storage
Das war's für die anfängliche Einrichtung des Projekts!
Code-Setup
Öffnen Sie die Datei App.tsx und entfernen Sie den unnötigen Teil.
Löschen Sie den gesamten Inhalt in App.css

Juhu! Jetzt erstellen wir unsere Themes! Öffnen Sie die Datei index.css und fügen Sie Folgendes hinzu
:root {
--background: white;
--text-primary: black;
--text-secondary: royalblue;
--accent: purple;
}
[data-theme='dark'] {
--background: black;
--text-primary: white;
--text-secondary: grey;
--accent: darkred;
}
Hier ist, was wir bisher haben:

Sehen Sie, was wir gerade getan haben? Wenn Sie mit CSS Custom Properties (auch bekannt als CSS-Variablen) nicht vertraut sind, ermöglichen sie uns, einen Wert zu definieren, der anderswo in unseren Stylesheets verwendet wird, mit dem Muster --key: value. In diesem Fall definieren wir nur ein paar Farben und wenden sie auf das :root-Element an, damit sie überall dort verwendet werden können, wo wir sie im gesamten React-Projekt benötigen.
Der zweite Teil, beginnend mit [data-theme='dark'], ist, wo die Dinge interessant werden. HTML (und JSX, das wir verwenden, um HTML in React zu erstellen) erlaubt uns, völlig beliebige Eigenschaften für unsere HTML-Elemente mit dem data-*-Attribut zu setzen. In diesem Fall geben wir dem äußersten <div>-Element unserer Anwendung ein data-theme-Attribut und wechseln seinen Wert zwischen light und dark. Wenn es dark ist, überschreibt der CSS[data-theme='dark']-Abschnitt die von uns in :root definierten Variablen, sodass jede Formatierung, die von diesen Variablen abhängt, ebenfalls umgeschaltet wird.
Lassen Sie uns das in die Praxis umsetzen. Zurück in App.tsx, geben wir React eine Möglichkeit, den Theme-Zustand zu verfolgen. Normalerweise würden wir etwas wie useState für lokalen Zustand oder Redux für globales Zustandsmanagement verwenden, aber wir möchten auch, dass die Theme-Auswahl des Benutzers bestehen bleibt, wenn er unsere App verlässt und später zurückkehrt. Während wir Redux und redux-persist verwenden könnten, ist das für unsere Bedürfnisse viel zu übertrieben.
Stattdessen verwenden wir den useLocalStorage Hook, den wir zuvor installiert haben. Er gibt uns eine Möglichkeit, Dinge im lokalen Speicher zu speichern, wie Sie vielleicht erwarten, aber als React Hook behält er den zustandsbezogenen Kenntnisstand darüber, was er mit localStorage macht, was unser Leben einfacher macht.
Einige von Ihnen denken vielleicht: „Oh nein, was ist, wenn die Seite gerendert wird, bevor mein JavaScript sich mit localStorage meldet und wir den gefürchteten „Flash of wrong theme“ bekommen?“ Aber das müssen Sie hier nicht befürchten, da unsere React-App vollständig clientseitig gerendert wird; die anfängliche HTML-Datei ist im Grunde ein Skelett mit einem einzigen <div>, an das React die App anhängt. Alle endgültigen HTML-Elemente werden von JavaScript generiert, nachdem localStorage überprüft wurde.
Importieren Sie also zuerst den Hook oben in App.tsx mit
import useLocalStorage from 'use-local-storage'
Dann verwenden wir ihn innerhalb unserer App-Komponente mit
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const [theme, setTheme] = useLocalStorage('theme', defaultDark ? 'dark' : 'light');
Das tut ein paar Dinge für uns. Erstens prüfen wir, ob der Benutzer eine Theme-Präferenz in seinen Browsereinstellungen festgelegt hat. Dann erstellen wir eine zustandsbezogene theme-Variable, die an localStorage gebunden ist, und die setTheme-Funktion, um theme zu aktualisieren. useLocalStorage fügt ein key:value-Paar zu localStorage hinzu, wenn es noch nicht existiert, das standardmäßig auf theme: "light" gesetzt ist, es sei denn, unsere matchMedia-Prüfung ergibt true, in diesem Fall ist es theme: "dark". Auf diese Weise handhaben wir beide Möglichkeiten elegant: die Theme-Einstellungen für einen wiederkehrenden Benutzer beibehalten oder standardmäßig seine Browsereinstellungen berücksichtigen, wenn wir es mit neuen Benutzern zu tun haben.
Als Nächstes fügen wir der App-Komponente ein paar kleine Inhalte hinzu, damit wir Elemente zum Stylen haben, zusammen mit einem Button und einer Funktion, die uns tatsächlich erlaubt, das Theme umzuschalten.

App.tsxDie Geheimzutat ist in Zeile 14, wo wir data-theme={theme} zu unserem <div> auf oberster Ebene hinzugefügt haben. Indem wir nun den Wert von theme ändern, wählen wir aus, ob wir die CSS-Variablen in :root mit denen im Abschnitt data-theme='dark' der Datei index.css überschreiben möchten oder nicht.
Das Letzte, was wir tun müssen, ist, einige Stile hinzuzufügen, die diese CSS-Variablen verwenden, die wir zuvor erstellt haben, und es wird laufen! Öffnen Sie App.css und fügen Sie diesen CSS-Code dort ein
.App {
color: var(--text-primary);
background-color: var(--background);
font-size: large;
font-weight: bold;
padding: 20px;
height: calc(100vh - 40px);
transition: all .5s;
}
button {
color: var(--text-primary);
background-color: var(--background);
border: 2px var(--text-primary) solid;
float: right;
transition: all .5s;
}
Jetzt hängen der Hintergrund und der Text für das Haupt-<div> sowie der Hintergrund, der Text und die Umrandung des <button> von den CSS-Variablen ab. Das bedeutet, wenn sich das Theme ändert, aktualisiert sich alles, was von diesen Variablen abhängt. Beachten Sie auch, dass wir transition: all .5s sowohl zu App als auch zu <button> für einen reibungslosen Übergang zwischen den Farbthemen hinzugefügt haben.
Nun gehen Sie zurück zum Browser, der die App ausführt, und hier ist, was wir erhalten
Tada! Fügen wir eine weitere Komponente hinzu, nur um zu zeigen, wie das System funktioniert, wenn wir eine echte App bauen. Wir fügen einen /components-Ordner in /src hinzu, einen /square-Ordner in /components und fügen eine Square.tsx und eine square.css hinzu, so

Importieren wir es zurück in App.tsx, so

Hier ist, was wir jetzt als Ergebnis haben
Und da haben wir es! Offensichtlich ist das ein ziemlich einfaches Szenario, bei dem wir nur ein Standard (helles) Theme und ein zweites (dunkles) Theme verwenden. Aber wenn Ihre Anwendung es erfordert, könnte dieses System verwendet werden, um mehrere Theme-Optionen zu implementieren. Persönlich denke ich darüber nach, meinem nächsten Projekt Optionen für hell, dunkel, schokolade und erdbeere zu geben – lassen Sie Ihrer Kreativität freien Lauf!
Bonus: Integration mit React Scoped CSS
Die Verwendung von React Scoped CSS ist meine bevorzugte Methode, um die CSS-Datei jeder Komponente gekapselt zu halten, um Namenskollisionen und unbeabsichtigte Stil-Vererbung zu vermeiden. Mein bisheriger Favorit dafür war CSS Modules, aber das hat den Nachteil, dass das DOM im Browser aussieht, als hätte ein Roboter alle Klassennamen geschrieben… weil genau das der Fall ist. Dieser Mangel an menschlicher Lesbarkeit macht das Debugging viel mühsamer, als es sein müsste. Hier kommt React Scoped CSS ins Spiel. Wir können weiterhin CSS (oder Sass) genau so schreiben, wie wir es getan haben, und die Ausgabe sieht aus, als hätte sie ein Mensch geschrieben.
Da das React Scoped CSS Repository vollständige und detaillierte Installationsanleitungen bietet, fasse ich diese hier nur kurz zusammen.
Installieren und konfigurieren Sie zuerst Create React App Configuration Override (CRACO) gemäß deren Anweisungen. Craco ist ein Tool, das es uns ermöglicht, die Standard-Webpack-Konfiguration, die in Create-React-App (CRA) gebündelt ist, zu überschreiben. Normalerweise müssen Sie ein Projekt „ejecten“, wenn Sie Webpack in einem CRA-Projekt anpassen möchten, was eine unumkehrbare Operation ist und Sie vollständig für alle Abhängigkeiten verantwortlich macht, die normalerweise von anderen gehandhabt werden. Sie möchten das Ejecten normalerweise vermeiden, es sei denn, Sie wissen wirklich, wirklich, was Sie tun, und haben einen guten Grund, diesen Weg zu gehen. Stattdessen erlaubt uns CRACO, einige kleinere Anpassungen an unserer Webpack-Konfiguration vorzunehmen, ohne dass die Dinge unübersichtlich werden.
Sobald das erledigt ist, installieren Sie das React Scoped CSS-Paket
npm i craco-plugin-scoped-css
(Die README-Anweisungen verwenden yarn zur Installation anstelle von npm, aber beides ist in Ordnung.) Jetzt, da es installiert ist, benennen Sie einfach die CSS-Dateien um, indem Sie .scoped vor .css hinzufügen, so
app.css -> app.scoped.css
Und wir müssen sicherstellen, dass wir einen neuen Namen für den Import dieser CSS-Datei in eine Komponente verwenden
import './app.css'; -> import './app.scoped.css';
Jetzt ist das gesamte CSS gekapselt, sodass es nur auf die Komponenten angewendet wird, in die es importiert wird. Es funktioniert durch die Verwendung von data-*-Eigenschaften, ähnlich wie unser Theming-System. Wenn also eine gescoped CSS-Datei in eine Komponente importiert wird, werden alle Elemente dieser Komponente mit einer Eigenschaft wie data-v-46ef2374 gekennzeichnet, und die Stile aus dieser Datei werden so umschlossen, dass sie nur auf Elemente mit dieser exakten Daten-Eigenschaft angewendet werden.
Das ist alles wunderbar, aber der kleine Trick, damit das mit diesem Theming-System funktioniert, ist, dass wir **nicht** wollen, dass die CSS-Variablen gekapselt werden; wir wollen, dass sie auf das gesamte Projekt angewendet werden. Daher ändern wir index.css einfach nicht so, dass es scoped enthält… mit anderen Worten, wir können diese CSS-Datei unverändert lassen. Das ist alles! Jetzt haben wir ein leistungsfähiges Theming-System, das im Einklang mit Scoped CSS funktioniert – wir leben den Traum!
Vielen Dank, dass Sie sich die Zeit genommen haben, diese Anleitung zu lesen. Wenn sie Ihnen geholfen hat, etwas Tolles zu bauen, würde ich mich freuen, davon zu hören!


Oh, sehr hilfreich!
Gute Arbeit, Abram!
Vielen Dank für Ihren Beitrag, ich hatte Schwierigkeiten, ein Theming auf diese genaue Weise zu realisieren, außer dass ich mit Nextjs arbeite.
Alles funktioniert gut, außer beim Neuladen. Wenn ich die Seite neu lade, kann ich sie nicht dazu bringen, das lokale Speichermedium zu überprüfen. Ich habe viel gesucht und
typeof window !== 'undefined'und auchuseEffectausprobiert, aber ich scheine nicht herauszufinden, was falsch ist.Ich habe derzeit einen einfachen Versuch basierend auf Ihrem Artikel (außer dass ich eine andere Methode zur Handhabung von CSS verwende).
Könnten Sie einen Blick darauf werfen?
https://github.com/beumsk/beumsk.github.io/blob/main/components/layout.js
https://beumsk.github.io/
Hallo Beumsk! Wenn Sie Nextjs mit der Standardkonfiguration verwenden, dann werden alle Ihre HTML-Seiten vorkompiliert, um von einem CDN verteilt oder dynamisch bei jeder Anfrage generiert zu werden (Server-Side Rendering). Mein Code ist für Client-Side Rendering als Single Page Application konzipiert (daher die obige Zeile zur Umgehung des Szenarios „Flash of wrong theme“).
Darüber hinaus bin ich nicht vertraut genug mit Next, um sicher zu sein, warum Sie nicht auf localStorage zugreifen können, tut mir leid. Viel Glück!
Ich habe https://beumsk.github.io/ gesehen und es sieht so aus, als wäre es bereits gelöst, oder? :)
Nein, wenn Sie aktualisieren, ist die Theme-Präferenz nicht richtig.
Hallo Beumsk, ich habe Ihren Code auf Github gesehen. Ihr Problem liegt in der Komponente layout.js, Zeile 16. Ändern Sie von „const“ zu „let“ oder „var“
Variablen, die mit const deklariert sind, behalten konstante Werte und in Ihrer Komponente ändern sie sich von hell zu dunkel.
Danke. Das war wirklich hilfreich
Danke Abram für das Tutorial!
Funktioniert bei mir einwandfrei.
Wenn Sie jedoch den Button in einer Kindkomponente einfügen müssten, wie würden Sie das tun?
Danke!!!
Danke, Abram, dieser Beitrag ist leicht verständlich!
Ich hatte jedoch ein Problem, dass sich mein Theme nicht ändert. Ich habe Stunden damit verbracht und weiß immer noch nicht, was falsch an meinem Code ist. Vielleicht kann jemand das Problem in meinem Code erkennen? Vielen Dank im Voraus!
https://codesandbox.io/s/toggle-theme-54rm0o?file=/src/App.js
Hallo, fügen Sie diesen CSS-Code zu Ihrer styles.scss-Datei hinzu
.app {
top: 0;
width: 100%;
height: 100vh;
position: absolute;
color: var(–text-primary);
background-color: var(–background);
}
Entschuldigung, ich habe vergessen, Ihnen zu sagen, dass Sie eine className zur App-Komponente hinzufügen müssen
Guter Fund! Das rockt lol