Startseite / Artikel / Anhaltendes Themenswitchen in React Native mit Context und Hooks

Anhaltendes Themenswitchen in React Native mit Context und Hooks

Erstellen Sie einen React Native-Themenwechsler mit Context, useState und useEffect: Tab-Navigation, ein Themenauswahlfenster, Persistenz mit AsyncStorage sowie ein Ladeindikator beim Starten.

1618 Wörter

Das Wechseln des Themes erscheint wie ein Styling-Problem, ist aber eigentlich ein Zustandsproblem: Das aktuelle Theme muss von jedem Bildschirm aus lesbar sein, von einem dieser Bildschirme aus änderbar und auch nach dem Neustart der App weiterhin vorhanden sein. Das macht es zu einer guten Übung, um Hooks an etwas Realistischerem als einem Zähler kennenzulernen. In dieser Anleitung wird eine kleine React Native-App mit zwei Tabblättern, einem Theme-Wähler und persistenter Speicherung erstellt, wobei useContext, useState und useEffect verwendet werden, um das zu ersetzen, was früher Klassenkomponenten sowie eine beträchtliche Menge an Boilerplate erforderte.

Warum Hooks zu diesem Problem passen

Hooks kamen mit React 16.8 hinzu, und React Native erhielt ab Version 0.59 stabile Unterstützung dafür. Zuvor bedeutete das Teilen eines Themes einen klassenbasierten Provider, der den Zustand verwaltete, Lebenszyklusmethoden zum Laden gespeicherter Einstellungen sowie Render-Props oder -Consumer, die über das gesamte Framework verstreut waren. Mit Hooks wird der Provider zu einem einfachen Funktionskomponenten, der Zustand befindet sich in useState, das Laden erfolgt in useEffect, und die Consumer lesen den Wert mit useContext. Die Logik befindet sich somit in einer einzigen kleinen Datei anstelle dessen, dass sie über verschiedene Lebenszyklusmethoden verteilt ist.

Die hier genannten Versionen bezeichnen den Zeitpunkt, ab dem dieser Ansatz erstmals möglich wurde; aktuelle React Native-Versionen unterstützen Hooks standardmäßig, sodass jedes neuere Projekt funktioniert.

Projekt einrichten

Man benötigt ein React Native-Projekt in Version 0,59 oder neuer. Ein neues Projekt kann mit react-native init RNThemeProvider erstellt werden (neuere Toolchains verwenden stattdessen die Community CLI oder Expo; prüfen Sie die aktuellen Anleitungsdokumente).

Zusätzlich werden zwei Bibliotheken hinzugefügt:

  • react-navigation bietet die tabbasierte Navigation.
  • AsyncStorage speichert das ausgewählte Theme auf dem Gerät. Es wurde aus dem React Native-Kern entfernt und wird nun als eigenständiges Community-Paket bereitgestellt (heute unter @react-native-async-storage/async-storage veröffentlicht), weshalb es separat installiert werden muss.

In älteren React Native-Versionen mussten nach der Installation auch native Module manuell verknüpft werden; mit dem Autolinking in modernen Versionen ist dieser Schritt in der Regel nicht mehr notwendig.

Organisierung der Dateien

Eine kleine, vorhersehbare Struktur hält die Themalogik getrennt von den Bildschirmen und der Navigation:

- src
  — components
    — TabBar.js # custom bottom tabbar component
  — core
    — themeProvider.js # custom hook for theming
    — themes.json # JSON array containing our themes
  — screens
    — Main.js # first tab
    — Settings.js # second tab
  — App.js # navigation part
— index.js # entry point for react-native
— package.json # dependencies

Die core-Ordner enthält alles, was mit Themen zu tun hat: themes.json mit den Themendefinitionen sowie themeProvider.js mit dem Kontext, dem Anbieter und den Hilfsfunktionen. Die Bildschirme befinden sich in screens, die benutzerdefinierte Tableiste in components, und App.js kümmert sich um die Navigation.

Aufbau der Navigationsstruktur

Fangen Sie ohne jegliches Theming an. In App.js erstellen Sie einen Tab-Navigator am unteren Rand mit zwei Tabs: Main für den Inhalt der Anwendung und Settings für Einstellungen. Jeder Tab verweist auf eine einfache Funktionskomponente in Main.js und Settings.js, die derzeit nur einige Platzhaltexte anzeigt.

Führen Sie die App in dieser Phase aus. Wenn zwei Tabellen angezeigt werden und Sie zwischen ihnen wechseln können, ist das Grundgerüst fertig, und jeder weitere Schritt fügt lediglich Funktionalitäten hinzu. Die Navigation-APIs haben sich in den verschiedenen Versionen von react-navigation geändert, daher sollten Sie die Anleitung für die von Ihnen installierte Version befolgen, anstatt ältere Beispiele wortwörtlich zu kopieren.

Themen definieren und die UI des Auswahlmoduls

Themen als Daten

Jedes Thema ist ein Objekt in themes.json mit drei Feldern: einer eindeutigen key, die es identifiziert, einer Hintergrundfarbe und einer Textfarbe. Das Speichern von Themen als JSON statt als Code macht deren Erweiterung einfach; ein Farbpalettengenerator wie Coolors ist eine schnelle Möglichkeit, Farbkombinationen zu finden, die gut zusammenpassen.

themeProvider.js importiert diese Datei und exportiert vorerst zwei Dinge: den vollständigen Array der Themen, der auf dem Einstellungsbildschirm angezeigt wird, sowie ein Standardthema (der zweite Eintrag im Array). Die Logik zum Anbieten der Themen kommt später.

Die Bildschirme und die Tableiste

Der Einstellungen-Bildschirm verwendet einen FlatList, der für jedes Theme eine Zeile anzeigt, wobei die Überschrift nach dem aktuellen Design des Themas gestaltet wird. Der Hauptbildschirm wendet ebenfalls das aktuelle Theme auf seinen Hintergrund und den Text an.

Zuletzt sollte auch die Tableiste das aktuelle Theme widerspiegeln. Ein benutzerdefinierter TabBar-Komponente in components/TabBar.js zeigt die Taben an und verwendet die Farbe des aktiven Tabs aus dem Theme. Sie wird im Tab-Navigator in App.js über die Optionen des Navigators als benutzerdefinierte Tableistenkomponente registriert.

Zu diesem Zeitpunkt wirkt die App thematisiert, aber nur mit dem fest kodierten Standard. Noch reagiert nichts auf Berührungen. Genau da kommen Hooks ins Spiel.

Teilen des Themes mit useContext

React Context ermöglicht es einem Wert, durch den Baum zu fließen, ohne bei jedem Level Props weitergeben zu müssen. Falls Context für Sie neu ist, erklärt die React Context-Dokumentation das Konzept.

In themeProvider.js erstellen Sie einen Theme-Context sowie ein Provider-Komponente, ThemeContextProvider. Umhüllen Sie den Navigator in App.js mit diesem Provider, damit alle Bildschirme sowie die Tab-Leiste darunter liegen.

Um den Zugriff auf den Kontext zu erleichtern, fügen Sie ein withTheme-Higher-Order-Komponente zum gleichen Datei hinzu. Diese liest den Kontext mit useContext ein und übermittelt das Theme als Eigenschaft an die umschließende Komponente. Aktualisieren Sie Main, Settings und TabBar so, dass sie über withTheme exportiert werden, sodass sie das aktuelle Theme erhalten, ohne zu wissen, woher es stammt. Die Leitfäden zu Higher-Order-Komponenten erklären dieses Muster ausführlicher.

A HOC funktioniert gut, wenn Komponenten bereits Props erwarten. In einer hooks-basierten Codebasis ist ein kleiner useTheme-Hook, der useContext(ThemeContext) zurückgibt, oft einfacher, vermeidet eine zusätzliche Wrapper-Schicht und macht die Abhängigkeit innerhalb der Komponente sichtbar. Unser Leitfaden zu Custom Hooks und der Wiederverwendung von Logik erklärt, warum Hooks Logik statt Zustand teilen – genau deshalb ist hier der Context weiterhin notwendig.

Das Theme mit useState ändern

Setzt nun den Picker in Betrieb. Innerhalb von ThemeContextProvider wird das aktuelle Theme mit useState gespeichert, initialisiert mit dem Standardtheme. Sowohl das Theme als auch eine setTheme-Funktion werden in den Context-Wert aufgenommen.

Auf dem Einstellungensbildschirm rufen Sie setTheme auf, wenn eine Zeile in der FlatList berührt wird. Da sich der Zustand des Providers ändert, wird jedes Komponente, das den Kontext liest, mit den neuen Farben neu gerendert: Die Bildschirme sowie die Tableiste wechseln sofort.

Ein Detail, das man übernehmen sollte: Wenn der Kontextwert bei jeder Neuerstellung des Providers ein neues Objekt ist, werden alle Verbraucherkomponenten jedes Mal neu gerendert, wenn der Provider es tut. Durch das Memorisieren des Wertes mit useMemo, wobei der Theme als Schlüssel verwendet wird, lässt sich das vermeiden. In einer so kleinen Anwendung spielt das kaum eine Rolle, wird aber relevant, sobald viele Komponenten den Kontext verbrauchen.

Aufbewahrung der Auswahl mit AsyncStorage

Das Anklicken eines Themes funktioniert jetzt, doch die Auswahl geht beim Neuladen verloren. Erweitern Sie setTheme, sodass es neben dem Aktualisieren des Zustands die Auswahl auch in AsyncStorage speichert. Es ist eine zuverlässigere Lösung, den key des Themes statt des gesamten Objekts zu speichern: Wenn Sie später die Farben eines Themes in themes.json anpassen, erhalten die Benutzer die aktualisierte Version anstelle einer veralteten Kopie.

Wiederherstellung des Themes beim Start mit useEffect

Der letzte Schritt besteht darin, das gespeicherte Theme beim Starten der App zu lesen. useEffect wird nach dem Rendern des Components ausgeführt, weshalb es der geeignete Ort für Nebeneffekte wie das Lesen aus dem Speicher ist. In Klassensprache entspricht dies dem, was früher componentDidMount und componentDidUpdate zur Handhabung solcher Aufgaben verwendet haben; der oft angeführte Vergleich mit componentWillReceiveProps ist irreführend, da die Effekte nach dem Renderen und nicht vor Erhalt neuer Props ausgeführt werden.

Innerhalb von ThemeContextProvider fügt man einen Effekt hinzu, der den in AsyncStorage gespeicherten Schlüssel liest, das entsprechende Theme findet und den State-Setter aufruft. Zwei Aspekte sorgen dafür, dass dies korrekt funktioniert:

  • Geben Sie ein leeres Abhängigkeitsarray an. Die Aktion sollte nur einmal ausgeführt werden, wenn der Provider initialisiert wird, und nicht nach jeder Neuansicht. Die Hooks-API-Dokumentation erklärt, wie das Abhängigkeitsarray bestimmt, wann eine Aktion ausgeführt wird.
  • Behandeln Sie den Ladezustand. AsyncStorage ist asynchron, sodass die erste Neuansicht vor dem Erkennen des gespeicherten Themas stattfindet. Überwachen Sie, ob das Laden abgeschlossen ist, und zeigen Sie bis dahin nichts an (oder eine Voransicht), andernfalls sehen die Benutzer vor dem Erscheinen ihrer eigenen Auswahl kurz das Standardthema.

Behandeln Sie außerdem den Fall, dass noch nichts gespeichert ist oder die gespeicherte Schlüsselangabe nicht mehr mit einem Thema übereinstimmt, indem Sie auf das Standardthema zurückgreifen. So bleibt die Auswahl auch nach einem Neustart der App erhalten.

Zusammenfassung

Der fertige Provider ist ein einzelnes Funktionskomponente, das den Theme-Zustand verwaltet, Änderungen speichert, diese beim Start wiederherstellt und alles über den Context bereitstellt. Im Vergleich zu einer klassenbasierten Version ist die Logik kürzer und lässt sich von oben nach unten lesen.

Mehrere Punkte gelten auch für andere Funktionen:

  • Halten Sie gemeinsame UI-Einstellungen in einem Context-Provider in der Nähe des Wurzelelements auf und stellen Sie zusammen mit dem Wert auch einen Setter bereit.
  • Speichern Sie Identifikatoren, nicht ganze Objekte, damit Datenänderungen keine veralteten Kopien auf den Geräten hinterlassen.
  • Betrachten Sie asynchrone Lesevorgänge beim Start als Ladezustand, anstatt vorab Standardwerte anzuzeigen und diese später zu ersetzen.
  • Sobald Sie sich mit diesen drei Hooks vertraut gemacht haben, schauen Sie sich useReducer für komplexere Zustände an, useRef für veränderliche Werte, die keine Neuzeichnung auslösen sollten, sowie useLayoutEffect für Aufgaben, die vor der Darstellung ausgeführt werden müssen. Die Umwandlung einer bestehenden Klassenkomponente ist eine gute Möglichkeit zum Üben.
  • Verwandte Artikel