Erstellen einer Web-App mit React, Redux und Sanity.io

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ Headless CMS ist eine leistungsstarke und einfache Möglichkeit, Inhalte zu verwalten und auf APIs zuzugreifen. Sanity.io baut auf React auf und ist ein nahtloses Tool für flexibles Content-Management. Es kann verwendet werden, um einfache bis komplexe Anwendungen von Grund auf zu erstellen. In diesem Artikel erklärt Ifeanyi, wie man mit Sanity.io und React eine einfache Listing-App erstellt. Die globalen Zustände werden mit Redux verwaltet und die Anwendung wird mit styled-components gestylt.

Die schnelle Entwicklung digitaler Plattformen hat traditionellen CMS wie Wordpress ernsthafte Einschränkungen auferlegt. Diese Plattformen sind gekoppelt, unflexibel und konzentrieren sich eher auf das Projekt als auf das Produkt. Glücklicherweise wurden mehrere Headless-CMS entwickelt, um diese und viele weitere Herausforderungen anzugehen.

Im Gegensatz zu herkömmlichen CMS kann Headless CMS, das als Software as a Service (SaaS) bezeichnet werden kann, zur Entwicklung von Websites, mobilen Apps, digitalen Displays und vielem mehr verwendet werden. Sie können auf unbegrenzten Plattformen verwendet werden. Wenn Sie nach einem CMS suchen, das plattformunabhängig und entwicklerorientiert ist und plattformübergreifende Unterstützung bietet, müssen Sie sich nicht weiter von Headless CMS entfernen.

Ein Headless CMS ist einfach ein CMS ohne Kopf. Der head bezieht sich hier auf das Frontend oder die Präsentationsschicht, während der body auf das Backend oder das Content Repository verweist. Dies bietet viele interessante Vorteile. Beispielsweise kann der Entwickler ein beliebiges Frontend seiner Wahl auswählen und Sie können auch die Präsentationsschicht nach Ihren Wünschen gestalten.

Es gibt viele Headless-CMS da draußen, einige der beliebtesten sind Strapi, Contentful, Contentstack, Sanity, Butter CMS, Prismic, Storyblok, Directus usw. Diese Headless-CMS sind API-basiert und haben ihre individuellen Stärken. Beispielsweise sind CMS wie Sanity, Strapi, Contentful und Storyblok für kleine Projekte kostenlos.

Diese Headless-CMS basieren ebenfalls auf verschiedenen Tech-Stacks. Während Sanity.io auf React.js basiert, basiert Storyblok auf Vue.js. Als React-Entwickler ist dies der Hauptgrund, warum ich mich schnell für Sanity interessiert habe. Da es sich jedoch um ein Headless-CMS handelt, kann jede dieser Plattformen an jedes Frontend angeschlossen werden, egal ob Angular, Vue oder React.

Jedes dieser kopflosen CMS hat sowohl kostenlose als auch kostenpflichtige Pläne, die einen erheblichen Preissprung darstellen. Obwohl diese kostenpflichtigen Pläne mehr Funktionen bieten, möchten Sie für ein kleines bis mittelgroßes Projekt nicht allzu viel bezahlen. Sanity versucht, dieses Problem zu lösen, indem es Pay-as-you-go-Optionen einführt. Mit diesen Optionen können Sie für das bezahlen, was Sie verwenden, und den Preissprung vermeiden.

Ein weiterer Grund, warum ich mich für Sanity.io entschieden habe, ist ihre GROQ-Sprache. Für mich hebt sich Sanity mit diesem Tool von der Masse ab. Graphical-Relational Object Queries (GROQ) verkürzt die Entwicklungszeit, hilft Ihnen, den benötigten Inhalt in der gewünschten Form zu erhalten, und hilft dem Entwickler auch, ein Dokument mit einem neuen Inhaltsmodell ohne Codeänderungen zu erstellen.

Darüber hinaus sind Entwickler nicht auf die GROQ-Sprache beschränkt. Sie können auch GraphQL oder sogar das traditionelle axios und fetch in Ihrer React-App verwenden, um das Backend abzufragen. Wie die meisten anderen Headless-CMS verfügt Sanity über eine umfassende Dokumentation mit hilfreichen Tipps zum Aufbau auf der Plattform.

Hinweis: Dieser Artikel erfordert ein grundlegendes Verständnis von React, Redux und CSS.

Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Erste Schritte mit Sanity.io

Um Sanity auf Ihrem Computer zu verwenden, müssen Sie das Sanity-CLI-Tool installieren. Obwohl dies lokal in Ihrem Projekt installiert werden kann, ist es vorzuziehen, es global zu installieren, um es für alle zukünftigen Anwendungen zugänglich zu machen.

Geben Sie dazu die folgenden Befehle in Ihr Terminal ein.

 npm install -g @sanity/cli

Das Flag -g im obigen Befehl aktiviert die globale Installation.

Als nächstes müssen wir Sanity in unserer Anwendung initialisieren. Obwohl dies als separates Projekt installiert werden kann, ist es normalerweise vorzuziehen, es innerhalb Ihrer Frontend-App (in diesem Fall React) zu installieren.

In ihrem Blog erklärte Kapehe ausführlich, wie man Sanity mit React integriert. Es ist hilfreich, den Artikel durchzugehen, bevor Sie mit diesem Tutorial fortfahren.

Geben Sie die folgenden Befehle ein, um Sanity in Ihrer React-App zu initialisieren.

 sanity init

Der sanity Befehl steht uns zur Verfügung, wenn wir das Sanity-CLI-Tool installiert haben. Sie können eine Liste der verfügbaren Sanity-Befehle anzeigen, indem Sie sanity oder sanity help in Ihr Terminal eingeben.

Wenn Sie Ihr Projekt einrichten oder initialisieren, müssen Sie den Eingabeaufforderungen folgen, um es anzupassen. Sie müssen auch einen Datensatz erstellen, und Sie können sogar einen benutzerdefinierten Datensatz auswählen, der mit Daten gefüllt ist. Für diese Auflistungs-App verwenden wir den benutzerdefinierten Sci-Fi-Filmdatensatz von Sanity. Dies erspart uns die eigene Eingabe der Daten.

Um Ihren Datensatz cd und zu bearbeiten, wechseln Sie in Ihr Terminal in das Unterverzeichnis Sanity und geben Sie sanity start . Dies läuft normalerweise auf https://localhost:3333/ . Möglicherweise müssen Sie sich anmelden, um auf die Benutzeroberfläche zuzugreifen (stellen Sie sicher, dass Sie sich mit demselben Konto anmelden, das Sie beim Initialisieren des Projekts verwendet haben). Ein Screenshot der Umgebung ist unten gezeigt.

Sanity-Serverübersicht
Eine Übersicht über den Sanity-Server für den Sci-Fi-Film-Datensatz. (Große Vorschau)

Sanity-React Zwei-Wege-Kommunikation

Sanity und React müssen für eine voll funktionsfähige Anwendung miteinander kommunizieren.

CORS Origins-Einstellung in Sanity Manager

Wir werden zuerst unsere React-App mit Sanity verbinden. Melden Sie sich dazu bei https://manage.sanity.io/ an und suchen Sie CORS origins unter API Settings auf der Registerkarte Settings . Hier müssen Sie Ihren Frontend-Ursprung mit dem Sanity-Backend verknüpfen. Unsere React-App läuft standardmäßig auf https://localhost:3000/ , also müssen wir das zu CORS hinzufügen.

Dies ist in der folgenden Abbildung dargestellt.

CORS-Ursprungseinstellungen
Festlegen des CORS-Ursprungs in Sanity.io Manager. (Große Vorschau)

Vernunft verbinden, um zu reagieren

Sanity ordnet jedem von Ihnen erstellten Projekt eine project ID zu. Diese ID wird benötigt, wenn Sie es mit Ihrer Frontend-Anwendung verbinden. Die Projekt-ID finden Sie in Ihrem Sanity Manager.

Das Backend kommuniziert mit React über eine Bibliothek namens sanity client . Sie müssen diese Bibliothek in Ihrem Sanity-Projekt installieren, indem Sie die folgenden Befehle eingeben.

 npm install @sanity/client

Erstellen Sie eine Datei sanitySetup.js (der Dateiname spielt keine Rolle) in Ihrem Projektordner src und geben Sie die folgenden React-Codes ein, um eine Verbindung zwischen Sanity und React herzustellen.

 import sanityClient from "@sanity/client" export default sanityClient({ projectId: PROJECT_ID, dataset: DATASET_NAME, useCdn: true });

Wir haben unsere projectId , den dataset name und eine boolesche useCdn an die Instanz des Sanity-Clients übergeben, die aus @sanity/client importiert wurde. Das funktioniert und verbindet unsere App mit dem Backend.

Nachdem wir nun die bidirektionale Verbindung fertiggestellt haben, können wir direkt mit dem Aufbau unseres Projekts beginnen.

Einrichten und Verbinden von Redux mit unserer App

Wir benötigen einige Abhängigkeiten, um mit Redux in unserer React-App zu arbeiten. Öffnen Sie Ihr Terminal in Ihrer React-Umgebung und geben Sie die folgenden Bash-Befehle ein.

 npm install redux react-redux redux-thunk

Redux ist eine globale Zustandsverwaltungsbibliothek, die mit den meisten Frontend-Frameworks und -Bibliotheken wie React verwendet werden kann. Wir benötigen jedoch ein zwischengeschaltetes Tool React react-redux , um die Kommunikation zwischen unserem Redux-Speicher und unserer React-Anwendung zu ermöglichen. Redux Thunk hilft uns, eine Funktion anstelle eines Aktionsobjekts von Redux zurückzugeben.

Während wir den gesamten Redux-Workflow in einer Datei schreiben könnten, ist es oft übersichtlicher und besser, unsere Anliegen zu trennen. Dazu teilen wir unseren Workflow in drei Dateien auf, nämlich actions , reducers und dann den store . Wir benötigen jedoch auch eine separate Datei zum Speichern der action types , auch constants .

Einrichten des Shops

Der Store ist die wichtigste Datei in Redux. Es organisiert und verpackt die Zustände und sendet sie an unsere React-Anwendung.

Hier ist die anfängliche Einrichtung unseres Redux-Speichers, die zum Verbinden unseres Redux-Workflows erforderlich ist.

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/"; export default createStore( reducers, applyMiddleware(thunk) );

Die Funktion createStore in dieser Datei benötigt drei Parameter: den reducer (erforderlich), den Anfangszustand und den Enhancer (normalerweise eine Middleware, in diesem Fall thunk , der über applyMiddleware wird). Unsere Reducer werden in einem reducers -Ordner gespeichert und wir kombinieren und exportieren sie in einer index.js -Datei im reducers -Ordner. Dies ist die Datei, die wir im obigen Code importiert haben. Wir werden uns diese Datei später noch einmal ansehen.

Einführung in die GROQ-Sprache von Sanity

Sanity geht mit der Einführung von GROQ noch einen Schritt weiter bei der Abfrage von JSON-Daten. GROQ steht für Graph-Relational Object Queries. Laut Sanity.io ist GROQ eine deklarative Abfragesprache, die entwickelt wurde, um Sammlungen von weitgehend schemalosen JSON-Dokumenten abzufragen.

Sanity bietet sogar den GROQ Playground an, um Entwicklern zu helfen, sich mit der Sprache vertraut zu machen. Um jedoch auf den Spielplatz zugreifen zu können, müssen Sie Sanity Vision installieren. Führen Sie sanity install @sanity/vision auf Ihrem Terminal aus, um es zu installieren.

GROQ hat eine ähnliche Syntax wie GraphQL, ist jedoch komprimierter und einfacher zu lesen. Darüber hinaus kann GROQ im Gegensatz zu GraphQL zur Abfrage von JSON-Daten verwendet werden.

Um beispielsweise jedes Element in unserem Filmdokument abzurufen, verwenden wir die folgende GROQ-Syntax.

 *[_type == "movie"]

Wenn wir jedoch nur die _ids und crewMembers in unserem Filmdokument abrufen möchten. Wir müssen diese Felder wie folgt spezifizieren.

 `*[_type == 'movie']{ _id, crewMembers }

Hier haben wir * verwendet, um GROQ mitzuteilen, dass wir jedes Dokument von _type movie wollen. _type ist ein Attribut unter der Filmsammlung. Wir können den Typ auch so zurückgeben, wie wir es bei _id und crewMembers wie folgt getan haben:

 *[_type == 'movie']{ _id, _type, crewMembers }

Wir werden mehr an GROQ arbeiten, indem wir es in unsere Redux-Aktionen implementieren, aber Sie können die Dokumentation von Sanity.io für GROQ überprüfen, um mehr darüber zu erfahren. Der GROQ-Abfrage-Spickzettel bietet viele Beispiele, die Ihnen helfen, die Abfragesprache zu beherrschen.

Konstanten einrichten

Wir brauchen Konstanten, um die Aktionstypen in jeder Phase des Redux-Workflows zu verfolgen. Konstanten helfen dabei, die Art der Aktion zu bestimmen, die zu jedem Zeitpunkt ausgeführt wird. Beispielsweise können wir nachverfolgen, wann die API geladen wird, vollständig geladen ist und wann ein Fehler auftritt.

Wir müssen Konstanten nicht unbedingt in einer separaten Datei definieren, aber der Einfachheit und Klarheit halber ist dies normalerweise die beste Vorgehensweise in Redux.

Per Konvention werden Konstanten in Javascript mit Großbuchstaben definiert. Wir werden hier die Best Practices befolgen, um unsere Konstanten zu definieren. Hier ist ein Beispiel einer Konstante zum Bezeichnen von Anforderungen zum Abrufen von bewegten Filmen.

 export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST";

Hier haben wir eine Konstante MOVIE_FETCH_REQUEST , die einen Aktionstyp von MOVIE_FETCH_REQUEST bezeichnet. Dies hilft uns, diesen Aktionstyp ohne Verwendung von strings einfach aufzurufen und Fehler zu vermeiden. Wir haben die Konstante auch exportiert, damit sie überall in unserem Projekt verfügbar ist.

In ähnlicher Weise können wir andere Konstanten zum Abrufen von Aktionstypen erstellen, die angeben, wann die Anforderung erfolgreich ist oder fehlschlägt. Ein vollständiger Code für movieConstants.js ist im folgenden Code angegeben.

 export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST"; export const MOVIE_FETCH_SUCCESS = "MOVIE_FETCH_SUCCESS"; export const MOVIE_FETCH_FAIL = "MOVIE_FETCH_FAIL"; export const MOVIES_FETCH_REQUEST = "MOVIES_FETCH_REQUEST"; export const MOVIES_FETCH_SUCCESS = "MOVIES_FETCH_SUCCESS"; export const MOVIES_FETCH_FAIL = "MOVIES_FETCH_FAIL"; export const MOVIES_FETCH_RESET = "MOVIES_FETCH_RESET"; export const MOVIES_REF_FETCH_REQUEST = "MOVIES_REF_FETCH_REQUEST"; export const MOVIES_REF_FETCH_SUCCESS = "MOVIES_REF_FETCH_SUCCESS"; export const MOVIES_REF_FETCH_FAIL = "MOVIES_REF_FETCH_FAIL"; export const MOVIES_SORT_REQUEST = "MOVIES_SORT_REQUEST"; export const MOVIES_SORT_SUCCESS = "MOVIES_SORT_SUCCESS"; export const MOVIES_SORT_FAIL = "MOVIES_SORT_FAIL"; export const MOVIES_MOST_POPULAR_REQUEST = "MOVIES_MOST_POPULAR_REQUEST"; export const MOVIES_MOST_POPULAR_SUCCESS = "MOVIES_MOST_POPULAR_SUCCESS"; export const MOVIES_MOST_POPULAR_FAIL = "MOVIES_MOST_POPULAR_FAIL";

Hier haben wir mehrere Konstanten zum Abrufen eines Films oder einer Liste von Filmen, zum Sortieren und Abrufen der beliebtesten Filme definiert. Beachten Sie , dass wir Konstanten setzen , um festzustellen , wann die Anfrage loading , successful und failed ist .

In ähnlicher Weise ist unsere personConstants.js -Datei unten angegeben:

 export const PERSONS_FETCH_REQUEST = "PERSONS_FETCH_REQUEST"; export const PERSONS_FETCH_SUCCESS = "PERSONS_FETCH_SUCCESS"; export const PERSONS_FETCH_FAIL = "PERSONS_FETCH_FAIL"; export const PERSON_FETCH_REQUEST = "PERSON_FETCH_REQUEST"; export const PERSON_FETCH_SUCCESS = "PERSON_FETCH_SUCCESS"; export const PERSON_FETCH_FAIL = "PERSON_FETCH_FAIL"; export const PERSONS_COUNT = "PERSONS_COUNT";

Wie movieConstants.js wir eine Liste von Konstanten zum Abrufen einer oder mehrerer Personen fest. Wir setzen auch eine Konstante für das Zählen von Personen. Die Konstanten folgen der Konvention, die für movieConstants.js beschrieben wurde, und wir haben sie auch exportiert, um für andere Teile unserer Anwendung zugänglich zu sein.

Schließlich implementieren wir den Hell- und Dunkelmodus in der App und haben so eine weitere Konstantendatei globalConstants.js . Werfen wir einen Blick darauf.

 export const SET_LIGHT_THEME = "SET_LIGHT_THEME"; export const SET_DARK_THEME = "SET_DARK_THEME";

Hier legen wir Konstanten fest, um zu bestimmen, wann der Hell- oder Dunkelmodus gesendet wird. SET_LIGHT_THEME bestimmt, wann der Benutzer zum hellen Design wechselt, und SET_DARK_THEME bestimmt, wann das dunkle Design ausgewählt wird. Wir haben auch unsere Konstanten wie gezeigt exportiert.

Einrichten der Aktionen

Konventionell werden unsere Aktionen in einem separaten Ordner gespeichert. Aktionen werden nach ihrem Typ gruppiert. Beispielsweise werden unsere Filmaktionen in movieActions.js gespeichert, während unsere Personenaktionen in der Datei personActions.js gespeichert werden.

Wir haben auch globalActions.js , um das Design vom hellen in den dunklen Modus umzuschalten.

Lassen Sie uns alle Filme in moviesActions.js .

 import sanityAPI from "../../sanitySetup"; import { MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS } from "../constants/movieConstants"; const fetchAllMovies = () => async (dispatch) => { try { dispatch({ type: MOVIES_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster": poster.asset->url, } ` ); dispatch({ type: MOVIES_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_FETCH_FAIL, payload: error.message }); } };

Erinnerst du dich, als wir die Datei sanitySetup.js erstellt haben, um React mit unserem Sanity-Backend zu verbinden? Hier haben wir das Setup importiert, damit wir unser Sanity-Backend mit GROQ abfragen können. Wir haben auch einige Konstanten importiert, die aus der Datei movieConstants.js in den constants exportiert wurden.

Als Nächstes haben wir die Aktionsfunktion fetchAllMovies zum Abrufen aller Filme in unserer Sammlung erstellt. Die meisten herkömmlichen React-Anwendungen verwenden axios oder fetch , um Daten vom Backend abzurufen. Aber während wir hier jedes davon verwenden könnten, verwenden wir GROQ von Sanity. Um in den GROQ Modus zu gelangen, müssen wir die Funktion sanityAPI.fetch() aufrufen, wie im obigen Code gezeigt. Hier ist sanityAPI die React-Sanity-Verbindung, die wir zuvor eingerichtet haben. Dies gibt ein Promise zurück und muss daher asynchron aufgerufen werden. Wir haben hier die async-await Syntax verwendet, aber wir können auch die .then Syntax verwenden.

Da wir in unserer Anwendung thunk verwenden, können wir anstelle eines Aktionsobjekts eine Funktion zurückgeben. Wir haben uns jedoch dafür entschieden, die return-Anweisung in einer Zeile zu übergeben.

 const fetchAllMovies = () => async (dispatch) => { ... }

Beachten Sie, dass wir die Funktion auch so schreiben können:

 const fetchAllMovies = () => { return async (dispatch)=>{ ... } }

Im Allgemeinen haben wir zum Abrufen aller Filme zuerst einen Aktionstyp gesendet, der nachverfolgt, wann die Anfrage noch geladen wird. Anschließend haben wir die GROQ-Syntax von Sanity verwendet, um das Filmdokument asynchron abzufragen. Wir haben die _id und die Poster-URL der Filmdaten abgerufen. Wir haben dann eine Nutzlast zurückgegeben, die die von der API erhaltenen Daten enthält.

Ebenso können wir Filme nach ihrer _id , Filme sortieren und die beliebtesten Filme abrufen.

Wir können auch Filme abrufen, die der Referenz einer bestimmten Person entsprechen. Wir haben dies in der Funktion fetchMoviesByRef .

 const fetchMoviesByRef = (ref) => async (dispatch) => { try { dispatch({ type: MOVIES_REF_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && (castMembers[person._ref match '${ref}'] || crewMembers[person._ref match '${ref}']) ]{ _id, "poster" : poster.asset->url, title } ` ); dispatch({ type: MOVIES_REF_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_REF_FETCH_FAIL, payload: error.message }); } };

Diese Funktion nimmt ein Argument und prüft, ob person._ref entweder in castMembers oder crewMembers mit dem übergebenen Argument übereinstimmt. Wir geben die Film- _id , die poster url und den title daneben zurück. Wir senden auch eine Aktion des Typs MOVIES_REF_FETCH_SUCCESS , die eine Nutzlast der zurückgegebenen Daten anhängt, und wenn ein Fehler auftritt, senden wir eine Aktion des Typs MOVIE_REF_FETCH_FAIL , die dank des try-catch Wrappers eine Nutzlast der Fehlermeldung anhängt.

In der Funktion fetchMovieById haben wir GROQ verwendet, um einen Film abzurufen, der mit einer bestimmten id übereinstimmt, die an die Funktion übergeben wurde.

Die GROQ Syntax für die Funktion ist unten dargestellt.

 const data = await sanityAPI.fetch( `*[_type == 'movie' && _id == '${id}']{ _id, "cast" : castMembers[]{ "ref": person._ref, characterName, "name": person->name, "image": person->image.asset->url } , "crew" : crewMembers[]{ "ref": person._ref, department, job, "name": person->name, "image": person->image.asset->url } , "overview": { "text": overview[0].children[0].text }, popularity, "poster" : poster.asset->url, releaseDate, title }[0]` );

Wie bei der Aktion fetchAllMovies begannen wir mit der Auswahl aller Dokumente des Typs movie , gingen aber weiter und wählten nur diejenigen aus, deren ID an die Funktion übergeben wurde. Da wir beabsichtigen, viele Details für den Film anzuzeigen, haben wir eine Reihe von abzurufenden Attributen angegeben.

Wir haben die Film- id und auch einige Attribute im Array castMembers abgerufen, nämlich ref , characterName , den Namen der Person und das Bild der Person. Wir haben auch den Alias ​​von castMembers in cast geändert.

Wie castMembers haben wir einige Attribute aus dem Array crewMembers ausgewählt, nämlich ref , department , job , den Namen der Person und das Bild der Person. wir haben auch den Alias ​​von crewMembers in crew geändert.

Auf die gleiche Weise haben wir den Übersichtstext, die Popularität, die Poster-URL des Films, das Erscheinungsdatum und den Titel des Films ausgewählt.

Die GROQ-Sprache von Sanity ermöglicht es uns auch, ein Dokument zu sortieren. Um einen Artikel zu sortieren, übergeben wir die Bestellung neben einem Pipe -Operator.

Wenn wir beispielsweise Filme nach ihrem releaseDate in aufsteigender Reihenfolge sortieren möchten, könnten wir Folgendes tun.

 const data = await sanityAPI.fetch( `*[_type == 'movie']{ ... } | order(releaseDate, asc)` );

Wir haben diesen Begriff in der sortMoviesBy Funktion verwendet, um entweder in aufsteigender oder absteigender Reihenfolge zu sortieren.

Schauen wir uns diese Funktion unten an.

 const sortMoviesBy = (item, type) => async (dispatch) => { try { dispatch({ type: MOVIES_SORT_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, title } | order( ${item} ${type})` ); dispatch({ type: MOVIES_SORT_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_SORT_FAIL, payload: error.message }); } };

Wir begannen damit, eine Aktion vom Typ MOVIES_SORT_REQUEST zu senden, um festzustellen, wann die Anfrage geladen wird. Wir haben dann die GROQ Syntax verwendet, um Daten aus der movie zu sortieren und abzurufen. Das Element, nach dem sortiert werden soll, wird in der Variablen item bereitgestellt, und der Sortiermodus (aufsteigend oder absteigend) wird in der Variablen type bereitgestellt. Folglich haben wir die id , die Poster-URL und den Titel zurückgegeben. Sobald die Daten zurückgegeben wurden, senden wir eine Aktion vom Typ MOVIES_SORT_SUCCESS , und wenn sie fehlschlägt, senden wir eine Aktion vom Typ MOVIES_SORT_FAIL .

Ein ähnliches GROQ Konzept gilt für die getMostPopular Funktion. Die GROQ Syntax ist unten dargestellt.

 const data = await sanityAPI.fetch( ` *[_type == 'movie']{ _id, "overview": { "text": overview[0].children[0].text }, "poster" : poster.asset->url, title }| order(popularity desc) [0..2]` );

Der einzige Unterschied hier ist, dass wir die Filme absteigend nach Beliebtheit sortiert und dann nur die ersten drei ausgewählt haben. Die Elemente werden in einem nullbasierten Index zurückgegeben, und daher sind die ersten drei Elemente die Elemente 0, 1 und 2. Wenn wir die ersten zehn Elemente abrufen möchten, könnten wir [0..9] an die Funktion übergeben.

Hier ist der vollständige Code für die Filmaktionen in der Datei movieActions.js .

 import sanityAPI from "../../sanitySetup"; import { MOVIE_FETCH_FAIL, MOVIE_FETCH_REQUEST, MOVIE_FETCH_SUCCESS, MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, MOVIES_SORT_REQUEST, MOVIES_SORT_SUCCESS, MOVIES_SORT_FAIL, MOVIES_MOST_POPULAR_REQUEST, MOVIES_MOST_POPULAR_SUCCESS, MOVIES_MOST_POPULAR_FAIL, MOVIES_REF_FETCH_SUCCESS, MOVIES_REF_FETCH_FAIL, MOVIES_REF_FETCH_REQUEST } from "../constants/movieConstants"; const fetchAllMovies = () => async (dispatch) => { try { dispatch({ type: MOVIES_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, } ` ); dispatch({ type: MOVIES_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_FETCH_FAIL, payload: error.message }); } }; const fetchMoviesByRef = (ref) => async (dispatch) => { try { dispatch({ type: MOVIES_REF_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && (castMembers[person._ref match '${ref}'] || crewMembers[person._ref match '${ref}']) ]{ _id, "poster" : poster.asset->url, title }` ); dispatch({ type: MOVIES_REF_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_REF_FETCH_FAIL, payload: error.message }); } }; const fetchMovieById = (id) => async (dispatch) => { try { dispatch({ type: MOVIE_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && _id == '${id}']{ _id, "cast" : castMembers[]{ "ref": person._ref, characterName, "name": person->name, "image": person->image.asset->url } , "crew" : crewMembers[]{ "ref": person._ref, department, job, "name": person->name, "image": person->image.asset->url } , "overview": { "text": overview[0].children[0].text }, popularity, "poster" : poster.asset->url, releaseDate, title }[0]` ); dispatch({ type: MOVIE_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIE_FETCH_FAIL, payload: error.message }); } }; const sortMoviesBy = (item, type) => async (dispatch) => { try { dispatch({ type: MOVIES_MOST_POPULAR_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, title } | order( ${item} ${type})` ); dispatch({ type: MOVIES_SORT_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_SORT_FAIL, payload: error.message }); } }; const getMostPopular = () => async (dispatch) => { try { dispatch({ type: MOVIES_SORT_REQUEST }); const data = await sanityAPI.fetch( ` *[_type == 'movie']{ _id, "overview": { "text": overview[0].children[0].text }, "poster" : poster.asset->url, title }| order(popularity desc) [0..2]` ); dispatch({ type: MOVIES_MOST_POPULAR_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_MOST_POPULAR_FAIL, payload: error.message }); } }; export { fetchAllMovies, fetchMovieById, sortMoviesBy, getMostPopular, fetchMoviesByRef };

Einrichten der Reduzierstücke

Reducer sind eines der wichtigsten Konzepte in Redux. Sie nehmen den vorherigen Zustand und bestimmen die Zustandsänderungen.

Normalerweise verwenden wir die switch-Anweisung, um eine Bedingung für jeden Aktionstyp auszuführen. Zum Beispiel können wir das loading zurückgeben, wenn der Aktionstyp das Laden anzeigt, und dann die Nutzlast, wenn es Erfolg oder Fehler anzeigt. Es wird erwartet, dass es den initial state und die action als Argumente aufnimmt.

Unsere Datei movieReducers.js enthält verschiedene Reduzierungen, die den in der Datei movieActions.js definierten Aktionen entsprechen. Jeder der Reducer hat jedoch eine ähnliche Syntax und Struktur. Die einzigen Unterschiede sind die constants , die sie aufrufen, und die Werte, die sie zurückgeben.

Sehen wir uns zunächst den fetchAllMoviesReducer in der Datei movieReducers.js an.

 import { MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, } from "../constants/movieConstants"; const fetchAllMoviesReducer = (state = {}, action) => { switch (action.type) { case MOVIES_FETCH_REQUEST: return { loading: true }; case MOVIES_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_FETCH_FAIL: return { loading: false, error: action.payload }; case MOVIES_FETCH_RESET: return {}; default: return state; } };

Wie alle Reducer nimmt der fetchAllMoviesReducer das Anfangszustandsobjekt ( state ) und das action als Argumente. Wir haben die switch-Anweisung verwendet, um die Aktionstypen zu jedem Zeitpunkt zu überprüfen. Wenn es MOVIES_FETCH_REQUEST entspricht, geben wir das Laden als wahr zurück, damit wir dem Benutzer einen Ladeindikator anzeigen können.

Wenn es MOVIES_FETCH_SUCCESS entspricht, schalten wir die Ladeanzeige aus und geben dann die Aktionsnutzlast in einer variablen movies zurück. Aber wenn es MOVIES_FETCH_FAIL ist, schalten wir auch das Laden aus und geben dann den Fehler zurück. Wir wollen auch die Option, unsere Filme zurückzusetzen. Dies wird es uns ermöglichen, die Zustände zu löschen, wenn wir dies tun müssen.

Wir haben die gleiche Struktur für andere Reduzierer. Die vollständige movieReducers.js ist unten dargestellt.

 import { MOVIE_FETCH_FAIL, MOVIE_FETCH_REQUEST, MOVIE_FETCH_SUCCESS, MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, MOVIES_SORT_REQUEST, MOVIES_SORT_SUCCESS, MOVIES_SORT_FAIL, MOVIES_MOST_POPULAR_REQUEST, MOVIES_MOST_POPULAR_SUCCESS, MOVIES_MOST_POPULAR_FAIL, MOVIES_FETCH_RESET, MOVIES_REF_FETCH_REQUEST, MOVIES_REF_FETCH_SUCCESS, MOVIES_REF_FETCH_FAIL } from "../constants/movieConstants"; const fetchAllMoviesReducer = (state = {}, action) => { switch (action.type) { case MOVIES_FETCH_REQUEST: return { loading: true }; case MOVIES_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_FETCH_FAIL: return { loading: false, error: action.payload }; case MOVIES_FETCH_RESET: return {}; default: return state; } }; const fetchMoviesByRefReducer = (state = {}, action) => { switch (action.type) { case MOVIES_REF_FETCH_REQUEST: return { loading: true }; case MOVIES_REF_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_REF_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const fetchMovieByIdReducer = (state = {}, action) => { switch (action.type) { case MOVIE_FETCH_REQUEST: return { loading: true }; case MOVIE_FETCH_SUCCESS: return { loading: false, movie: action.payload }; case MOVIE_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const sortMoviesByReducer = (state = {}, action) => { switch (action.type) { case MOVIES_SORT_REQUEST: return { loading: true }; case MOVIES_SORT_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_SORT_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const getMostPopularReducer = (state = {}, action) => { switch (action.type) { case MOVIES_MOST_POPULAR_REQUEST: return { loading: true }; case MOVIES_MOST_POPULAR_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_MOST_POPULAR_FAIL: return { loading: false, error: action.payload }; default: return state; } }; export { fetchAllMoviesReducer, fetchMovieByIdReducer, sortMoviesByReducer, getMostPopularReducer, fetchMoviesByRefReducer };

Wir folgten auch der exakt gleichen Struktur für personReducers.js . Beispielsweise definiert die Funktion fetchAllPersonsReducer die Zustände zum Abrufen aller Personen in der Datenbank.

Dies ist im folgenden Code angegeben.

 import { PERSONS_FETCH_FAIL, PERSONS_FETCH_REQUEST, PERSONS_FETCH_SUCCESS, } from "../constants/personConstants"; const fetchAllPersonsReducer = (state = {}, action) => { switch (action.type) { case PERSONS_FETCH_REQUEST: return { loading: true }; case PERSONS_FETCH_SUCCESS: return { loading: false, persons: action.payload }; case PERSONS_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } };

Genau wie fetchAllMoviesReducer haben wir fetchAllPersonsReducer mit state und action als Argumenten definiert. Dies sind Standardeinstellungen für Redux-Reduzierer. Wir haben dann die switch-Anweisung verwendet, um die Aktionstypen zu überprüfen, und wenn sie vom Typ PERSONS_FETCH_REQUEST ist, geben wir das Laden als wahr zurück. Wenn es PERSONS_FETCH_SUCCESS ist, schalten wir das Laden ab und geben die Nutzlast zurück, und wenn es PERSONS_FETCH_FAIL ist, geben wir den Fehler zurück.

Reduzierer kombinieren

Die combineReducers -Funktion von Redux ermöglicht es uns, mehr als einen Reducer zu kombinieren und an den Store zu übergeben. Wir kombinieren unsere Film- und Personenreduzierer in einer index.js -Datei im Ordner „ reducers “.

Werfen wir einen Blick darauf.

 import { combineReducers } from "redux"; import { fetchAllMoviesReducer, fetchMovieByIdReducer, sortMoviesByReducer, getMostPopularReducer, fetchMoviesByRefReducer } from "./movieReducers"; import { fetchAllPersonsReducer, fetchPersonByIdReducer, countPersonsReducer } from "./personReducers"; import { toggleTheme } from "./globalReducers"; export default combineReducers({ fetchAllMoviesReducer, fetchMovieByIdReducer, fetchAllPersonsReducer, fetchPersonByIdReducer, sortMoviesByReducer, getMostPopularReducer, countPersonsReducer, fetchMoviesByRefReducer, toggleTheme });

Hier haben wir alle Reduzierer aus der Film-, Personen- und globalen Reduziererdatei importiert und an die combineReducers Funktion übergeben. Die combineReducers Funktion nimmt ein Objekt, das es uns ermöglicht, alle unsere Reducer zu passieren. Wir können den Argumenten im Prozess sogar einen Alias ​​hinzufügen.

Wir werden später an den globalReducers arbeiten.

Wir können jetzt die Reducer in der Datei Redux store.js . Dies wird unten gezeigt.

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/index"; export default createStore(reducers, initialState, applyMiddleware(thunk));

Nachdem wir unseren Redux-Workflow eingerichtet haben, richten wir unsere React-Anwendung ein.

Einrichten unserer React-Anwendung

Unsere Reaktionsanwendung listet Filme und ihre entsprechenden Darsteller und Crewmitglieder auf. Wir werden „ react-router-dom “ für das Routing und „ styled-components “ für das Styling der App verwenden. Wir werden Material UI auch für Symbole und einige UI-Komponenten verwenden.

Geben Sie den folgenden bash Befehl ein, um die Abhängigkeiten zu installieren.

 npm install react-router-dom @material-ui/core @material-ui/icons query-string

Folgendes werden wir bauen:

Verbinden von Redux mit unserer React-App

React-redux wird mit einer Provider -Funktion ausgeliefert, die es uns ermöglicht, unsere Anwendung mit dem Redux-Store zu verbinden. Dazu müssen wir eine Instanz des Stores an den Provider übergeben. Wir können dies entweder in unserer Datei index.js oder App.js .

Hier ist unsere index.js-Datei.

 import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import { Provider } from "react-redux"; import store from "./redux/store"; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") );

Hier haben wir Provider aus react-redux und store aus unserem Redux-Store importiert. Dann haben wir unseren gesamten Komponentenbaum mit dem Provider umschlossen und den Store an ihn übergeben.

Als Nächstes benötigen wir den React react-router-dom für das Routing in unserer React-Anwendung. React react-router-dom wird mit BrowserRouter , Switch und Route geliefert, die verwendet werden können, um unseren Pfad und unsere Routen zu definieren.

Wir tun dies in unserer App.js -Datei. Dies wird unten gezeigt.

 import React from "react"; import Header from "./components/Header"; import Footer from "./components/Footer"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import MoviesList from "./pages/MoviesListPage"; import PersonsList from "./pages/PersonsListPage"; function App() { return ( <Router> <main className="contentwrap"> <Header /> <Switch> <Route path="/persons/"> <PersonsList /> </Route> <Route path="/" exact> <MoviesList /> </Route> </Switch> </main> <Footer /> </Router> ); } export default App;

Dies ist ein Standard-Setup für das Routing mit React-Router-Dom. Sie können es in ihrer Dokumentation überprüfen. Wir haben unsere Komponenten Header , Footer , PersonsList und MovieList . Wir richten dann den react-router-dom ein, indem wir alles in Router und Switch einpacken.

Da wir möchten, dass unsere Seiten dieselbe Kopf- und Fußzeile verwenden, mussten wir die Komponenten <Header /> und <Footer /> übergeben, bevor wir die Struktur mit Switch konnten. Wir haben auch mit dem main etwas Ähnliches gemacht, da wir möchten, dass es die gesamte Anwendung umschließt.

Wir haben jede Komponente mit Route from react-router-dom an die Route übergeben.

Definieren unserer Seiten und Komponenten

Unsere Bewerbung ist strukturiert aufgebaut. Wiederverwendbare Komponenten werden im components gespeichert, während pages im Seitenordner gespeichert werden.

Unsere pages umfassen movieListPage.js , moviePage.js , PersonListPage.js und PersonPage.js . Die MovieListPage.js listet alle Filme in unserem Sanity.io-Backend sowie die beliebtesten Filme auf.

Um alle Filme aufzulisten, movieAction.js wir einfach die Aktion dispatch , die in unserer Datei fetchAllMovies definiert ist. Da wir die Liste abrufen müssen, sobald die Seite geladen wird, müssen wir sie in useEffect definieren. Dies wird unten gezeigt.

 import React, { useEffect } from "react"; import { fetchAllMovies } from "../redux/actions/movieActions"; import { useDispatch, useSelector } from "react-redux"; const MoviesListPage = () => { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchAllMovies()); }, [dispatch]); const { loading, error, movies } = useSelector( (state) => state.fetchAllMoviesReducer ); return ( ... ) }; export default MoviesListPage;

Dank der Hooks useDispatch und useSelector können wir Redux-Aktionen versenden und die entsprechenden Zustände aus dem Redux-Speicher auswählen. Beachten Sie, dass die Zustände loading , error und movies in unseren Reducer-Funktionen definiert und hier mit dem useSelector Hook von React Redux ausgewählt wurden. Diese Zustände, nämlich loading , error und movies , werden verfügbar, sobald wir die fetchAllMovies() Aktionen ausgeführt haben.

Sobald wir die Liste der Filme erhalten haben, können wir sie in unserer Anwendung mithilfe der map oder wie auch immer wir möchten anzeigen.

Hier ist der vollständige Code für die Datei moviesListPage.js .

import React, {useState, useEffect} from 'react' import {fetchAllMovies, getMostPopular, sortMoviesBy} from "../redux/actions/movieActions" import {useDispatch, useSelector} from "react-redux" import Loader from "../components/BackdropLoader" import {MovieListContainer} from "../styles/MovieStyles.js" import SortIcon from '@material-ui/icons/Sort'; import SortModal from "../components/Modal" import {useLocation, Link} from "react-router-dom" import queryString from "query-string" import {MOVIES_FETCH_RESET} from "../redux/constants/movieConstants" const MoviesListPage = () => { const location = useLocation() const dispatch = useDispatch() const [openSort, setOpenSort] = useState(false) useEffect(()=>{ dispatch(getMostPopular()) const {order, type} = queryString.parse(location.search) if(order && type){ dispatch({ type: MOVIES_FETCH_RESET }) dispatch(sortMoviesBy(order, type)) }else{ dispatch(fetchAllMovies()) } }, [dispatch, location.search]) const {loading: popularLoading, error: popularError, movies: popularMovies } = useSelector(state => state.getMostPopularReducer) const { loading: moviesLoading, error: moviesError, movies } = useSelector(state => state.fetchAllMoviesReducer) const { loading: sortLoading, error: sortError, movies: sortMovies } = useSelector(state => state.sortMoviesByReducer) return ( <MovieListContainer> <div className="mostpopular"> { popularLoading ? <Loader /> : popularError ? popularError : popularMovies && popularMovies.map(movie => ( <Link to={`/movie?id=${movie._id}`} className="popular" key={movie._id} style={{backgroundImage: `url(${movie.poster})`}}> <div className="content"> <h2>{movie.title}</h2> <p>{movie.overview.text.substring(0, 50)}…</p> </div> </Link> )) } </div> <div className="moviespanel"> <div className="top"> <h2>All Movies</h2> <SortIcon onClick={()=> setOpenSort(true)} /> </div> <div className="movieslist"> { moviesLoading ? <Loader /> : moviesError ? moviesError : movies && movies.map(movie =>( <Link to={`/movie?id=${movie._id}`} key={movie._id}> <img className="movie" src={movie.poster} alt={movie.title} /> </Link> )) } { ( sortLoading ? !movies && <Loader /> : sortError ? sortError : sortMovies && sortMovies.map(movie =>( <Link to={`/movie?id=${movie._id}`} key={movie._id}> <img className="movie" src={movie.poster} alt={movie.title} /> </Link> )) ) } </div> </div> <SortModal open={openSort} setOpen={setOpenSort} /> </MovieListContainer> ) } export default MoviesListPage

Wir haben damit begonnen, die Aktion getMostPopular movies (diese Aktion wählt die Filme mit der höchsten Beliebtheit) im Hook useEffect . Dadurch können wir die beliebtesten Filme abrufen, sobald die Seite geladen wird. Außerdem haben wir Benutzern erlaubt, Filme nach releaseDate und popularity zu sortieren . Dies wird von der sortMoviesBy Aktion behandelt, die im obigen Code abgesetzt wird. Außerdem haben wir die fetchAllMovies abhängig von den Abfrageparametern versendet.

Außerdem haben wir den useSelector Hook verwendet, um die entsprechenden Reduzierungen für jede dieser Aktionen auszuwählen. Wir haben die Zustände für loading , error und movies für jeden der Reduzierer ausgewählt.

Nachdem wir die movies von den Reduzierern erhalten haben, können wir sie jetzt dem Benutzer anzeigen. Hier haben wir dazu die map von ES6 verwendet. Wir haben zuerst einen Loader angezeigt, wenn jeder der Filmstatus geladen wird, und wenn ein Fehler auftritt, zeigen wir die Fehlermeldung an. Wenn wir schließlich einen Film erhalten, zeigen wir dem Benutzer das Filmbild mithilfe der map an. Wir haben die gesamte Komponente in eine MovieListContainer Komponente verpackt.

Das Tag <MovieListContainer> … </MovieListContainer> ist ein div , das mithilfe von Stilkomponenten definiert wird. Das schauen wir uns gleich mal kurz an.

Gestalten unserer App mit gestylten Komponenten

Styled Components ermöglichen es uns, unsere Seiten und Komponenten individuell zu gestalten. Es bietet auch einige interessante Funktionen wie inheritance , Theming , passing of props usw.

Obwohl wir unsere Seiten immer individuell gestalten möchten, kann manchmal ein globales Styling wünschenswert sein. Interessanterweise bieten styled-components dank der createGlobalStyle Funktion eine Möglichkeit dazu.

Um Styled-Components in unserer Anwendung zu verwenden, müssen wir sie installieren. Öffnen Sie Ihr Terminal in Ihrem Reaktionsprojekt und geben Sie den folgenden bash Befehl ein.

 npm install styled-components

Nachdem Sie styled-components installiert haben, beginnen wir mit unseren globalen Styles.

Erstellen wir einen separaten Ordner in unserem src -Verzeichnis mit dem Namen styles . Dies speichert alle unsere Styles. Lassen Sie uns auch eine globalStyles.js -Datei im Styles-Ordner erstellen. Um einen globalen Stil in gestylten Komponenten zu erstellen, müssen wir createGlobalStyle importieren.

 import { createGlobalStyle } from "styled-components";

Wir können dann unsere Stile wie folgt definieren:

 export const GlobalStyle = createGlobalStyle` ... `

Stilisierte Komponenten verwenden das Vorlagenliteral, um Requisiten zu definieren. Innerhalb dieses Literals können wir unsere traditionellen CSS Codes schreiben.

Wir haben auch die in einer Datei namens definition.js definierte deviceWidth importiert. Die deviceWidth enthält die Definition von Breakpoints zum Setzen unserer Medienabfragen.

 import { deviceWidth } from "./definition";

Wir setzen overflow auf hidden, um den Ablauf unserer Anwendung zu steuern.

 html, body{ overflow-x: hidden; }

Wir haben auch den Kopfzeilenstil mit der .header -Stilauswahl definiert.

 .header{ z-index: 5; background-color: ${(props)=>props.theme.midDarkBlue}; display:flex; align-items:center; padding: 0 20px; height:50px; justify-content:space-between; position:fixed; top:0; width:100%; @media ${deviceWidth.laptop_lg} { width:97%; } ... }

Hier werden verschiedene Stile wie Hintergrundfarbe, Z-Index, Padding und viele andere traditionelle CSS-Eigenschaften definiert.

Wir haben die Stilkomponenten- props verwendet, um die Hintergrundfarbe festzulegen. Dadurch können wir dynamische Variablen setzen, die von unserer Komponente übergeben werden können. Darüber hinaus haben wir auch die Variable des Themas übergeben, damit wir das Beste aus unserem Thema herausholen können.

Theming ist hier möglich, weil wir unsere gesamte Anwendung mit dem ThemeProvider von styled-components umhüllt haben. Wir werden gleich darüber sprechen. Darüber hinaus haben wir die CSS flexbox verwendet, um unseren Header richtig zu formatieren und die Position auf fixed zu setzen, um sicherzustellen, dass sie in Bezug auf den Browser fixiert bleibt. Wir haben auch die Breakpoints definiert, um die Kopfzeilen mobilfreundlich zu gestalten.

Hier ist der vollständige Code für unsere globalStyles.js -Datei.

 import { createGlobalStyle } from "styled-components"; import { deviceWidth } from "./definition"; export const GlobalStyle = createGlobalStyle` html{ overflow-x: hidden; } body{ background-color: ${(props) => props.theme.lighter}; overflow-x: hidden; min-height: 100vh; display: grid; grid-template-rows: auto 1fr auto; } #root{ display: grid; flex-direction: column; } h1,h2,h3, label{ font-family: 'Aclonica', sans-serif; } h1, h2, h3, p, span:not(.MuiIconButton-label), div:not(.PrivateRadioButtonIcon-root-8), div:not(.tryingthis){ color: ${(props) => props.theme.bodyText} } p, span, div, input{ font-family: 'Jost', sans-serif; } .paginate button{ color: ${(props) => props.theme.bodyText} } .header{ z-index: 5; background-color: ${(props) => props.theme.midDarkBlue}; display: flex; align-items: center; padding: 0 20px; height: 50px; justify-content: space-between; position: fixed; top: 0; width: 100%; @media ${deviceWidth.laptop_lg}{ width: 97%; } @media ${deviceWidth.tablet}{ width: 100%; justify-content: space-around; } a{ text-decoration: none; } label{ cursor: pointer; color: ${(props) => props.theme.goldish}; font-size: 1.5rem; } .hamburger{ cursor: pointer; color: ${(props) => props.theme.white}; @media ${deviceWidth.desktop}{ display: none; } @media ${deviceWidth.tablet}{ display: block; } } } .mobileHeader{ z-index: 5; background-color: ${(props) => props.theme.darkBlue}; color: ${(props) => props.theme.white}; display: grid; place-items: center; width: 100%; @media ${deviceWidth.tablet}{ width: 100%; } height: calc(100% - 50px); transition: all 0.5s ease-in-out; position: fixed; right: 0; top: 50px; .menuitems{ display: flex; box-shadow: 0 0 5px ${(props) => props.theme.lightshadowtheme}; flex-direction: column; align-items: center; justify-content: space-around; height: 60%; width: 40%; a{ display: flex; flex-direction: column; align-items:center; cursor: pointer; color: ${(props) => props.theme.white}; text-decoration: none; &:hover{ border-bottom: 2px solid ${(props) => props.theme.goldish}; .MuiSvgIcon-root{ color: ${(props) => props.theme.lightred} } } } } } footer{ min-height: 30px; margin-top: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.875rem; background-color: ${(props) => props.theme.midDarkBlue}; color: ${(props) => props.theme.white}; } `;

Beachten Sie, dass wir reinen CSS-Code innerhalb des Literals geschrieben haben, aber es gibt ein paar Ausnahmen. Gestylte Komponenten ermöglichen es uns, Requisiten zu übergeben. Mehr dazu erfahren Sie in der Dokumentation.

Neben der Definition globaler Stile können wir Stile für einzelne Seiten definieren.

Hier ist beispielsweise der Stil für die PersonListPage.js , die in PersonStyle.js im Ordner „ styles “ definiert ist.

 import styled from "styled-components"; import { deviceWidth, colors } from "./definition"; export const PersonsListContainer = styled.div` margin: 50px 80px; @media ${deviceWidth.tablet} { margin: 50px 10px; } a { text-decoration: none; } .top { display: flex; justify-content: flex-end; padding: 5px; .MuiSvgIcon-root { cursor: pointer; &:hover { color: ${colors.darkred}; } } } .personslist { margin-top: 20px; display: grid; place-items: center; grid-template-columns: repeat(5, 1fr); @media ${deviceWidth.laptop} { grid-template-columns: repeat(4, 1fr); } @media ${deviceWidth.tablet} { grid-template-columns: repeat(3, 1fr); } @media ${deviceWidth.tablet_md} { grid-template-columns: repeat(2, 1fr); } @media ${deviceWidth.mobile_lg} { grid-template-columns: repeat(1, 1fr); } grid-gap: 30px; .person { width: 200px; position: relative; img { width: 100%; } .content { position: absolute; bottom: 0; left: 8px; border-right: 2px solid ${colors.goldish}; border-left: 2px solid ${colors.goldish}; border-radius: 10px; width: 80%; margin: 20px auto; padding: 8px 10px; background-color: ${colors.transparentWhite}; color: ${colors.darkBlue}; h2 { font-size: 1.2rem; } } } } `;

Wir haben zuerst styled aus styled-components und deviceWidth aus der definition importiert. Wir haben dann PersonsListContainer als div definiert, um unsere Stile zu speichern. Mithilfe von Medienabfragen und den festgelegten Haltepunkten haben wir die Seite mobilfreundlich gemacht, indem wir verschiedene Haltepunkte gesetzt haben.

Hier haben wir nur die Standard-Browser-Breakpoints für kleine, große und sehr große Bildschirme verwendet. Wir haben auch das Beste aus der CSS-Flexbox und dem Grid gemacht, um unsere Inhalte richtig zu gestalten und auf der Seite anzuzeigen.

Um diesen Stil in unserer PersonListPage.js -Datei zu verwenden, haben wir ihn einfach importiert und wie folgt zu unserer Seite hinzugefügt.

 import React from "react"; const PersonsListPage = () => { return ( <PersonsListContainer> ... </PersonsListContainer> ); }; export default PersonsListPage;

Der Wrapper gibt ein div aus, da wir es in unseren Stilen als div definiert haben.

Themen hinzufügen und einpacken

Es ist immer eine coole Funktion, Themen zu unserer Anwendung hinzuzufügen. Dazu benötigen wir Folgendes:

  • Unsere benutzerdefinierten Designs, die in einer separaten Datei definiert sind (in unserem Fall die Datei definition.js ).
  • Die in unseren Redux-Aktionen und -Reduzierern definierte Logik.
  • Unser Thema in unserer Anwendung aufrufen und durch den Komponentenbaum leiten.

Lassen Sie uns das überprüfen.

Hier ist unser theme in der Datei definition.js .

 export const theme = { light: { dark: "#0B0C10", darkBlue: "#253858", midDarkBlue: "#42526e", lightBlue: "#0065ff", normal: "#dcdcdd", lighter: "#F4F5F7", white: "#FFFFFF", darkred: "#E85A4F", lightred: "#E98074", goldish: "#FFC400", bodyText: "#0B0C10", lightshadowtheme: "rgba(0, 0, 0, 0.1)" }, dark: { dark: "white", darkBlue: "#06090F", midDarkBlue: "#161B22", normal: "#dcdcdd", lighter: "#06090F", white: "white", darkred: "#E85A4F", lightred: "#E98074", goldish: "#FFC400", bodyText: "white", lightshadowtheme: "rgba(255, 255, 255, 0.9)" } };

Wir haben verschiedene Farbeigenschaften für die hellen und dunklen Themen hinzugefügt. Die Farben wurden sorgfältig ausgewählt, um die Sichtbarkeit sowohl im hellen als auch im dunklen Modus zu ermöglichen. Sie können Ihre Themen nach Belieben definieren. Dies ist keine feste Regel.

Als Nächstes fügen wir die Funktionalität zu Redux hinzu.

Wir haben globalActions.js in unserem Redux-Aktionsordner erstellt und die folgenden Codes hinzugefügt.

 import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants"; import { theme } from "../../styles/definition"; export const switchToLightTheme = () => (dispatch) => { dispatch({ type: SET_LIGHT_THEME, payload: theme.light }); localStorage.setItem("theme", JSON.stringify(theme.light)); localStorage.setItem("light", JSON.stringify(true)); }; export const switchToDarkTheme = () => (dispatch) => { dispatch({ type: SET_DARK_THEME, payload: theme.dark }); localStorage.setItem("theme", JSON.stringify(theme.dark)); localStorage.setItem("light", JSON.stringify(false)); };

Hier haben wir einfach unsere definierten Themen importiert. Die entsprechenden Aktionen abgeschickt und die Payload der von uns benötigten Themen übergeben. Die Payload-Ergebnisse werden im lokalen Speicher unter Verwendung derselben Schlüssel für helle und dunkle Themen gespeichert. Dadurch können wir die Zustände im Browser beibehalten.

Wir müssen auch unseren Reduzierer für die Themen definieren.

 import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants"; export const toggleTheme = (state = {}, action) => { switch (action.type) { case SET_LIGHT_THEME: return { theme: action.payload, light: true }; case SET_DARK_THEME: return { theme: action.payload, light: false }; default: return state; } };

Das ist dem, was wir getan haben, sehr ähnlich. Wir haben die switch -Anweisung verwendet, um die Art der Aktion zu überprüfen, und dann die entsprechende payload zurückgegeben. Wir haben auch ein light zurückgegeben, das bestimmt, ob der Benutzer ein helles oder ein dunkles Design ausgewählt hat. Wir werden dies in unseren Komponenten verwenden.

Wir müssen es auch zu unserem Wurzelreduzierer hinzufügen und speichern. Hier ist der vollständige Code für unsere store.js .

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import { theme as initialTheme } from "../styles/definition"; import reducers from "./reducers/index"; const theme = localStorage.getItem("theme") ? JSON.parse(localStorage.getItem("theme")) : initialTheme.light; const light = localStorage.getItem("light") ? JSON.parse(localStorage.getItem("light")) : true; const initialState = { toggleTheme: { light, theme } }; export default createStore(reducers, initialState, applyMiddleware(thunk));

Da wir das Design beibehalten mussten, wenn der Benutzer aktualisiert, mussten wir es mit localStorage.getItem() aus dem lokalen Speicher holen und es in unseren Anfangszustand übergeben.

Hinzufügen der Funktionalität zu unserer React-Anwendung

Gestylte Komponenten stellen uns ThemeProvider zur Verfügung, die es uns ermöglichen, Themen durch unsere Anwendung zu übergeben. Wir können unsere App.js-Datei ändern, um diese Funktionalität hinzuzufügen.

Werfen wir einen Blick darauf.

 import React from "react"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import { useSelector } from "react-redux"; import { ThemeProvider } from "styled-components"; function App() { const { theme } = useSelector((state) => state.toggleTheme); let Theme = theme ? theme : {}; return ( <ThemeProvider theme={Theme}> <Router> ... </Router> </ThemeProvider> ); } export default App;

Indem wir Themes durch den ThemeProvider , können wir die Theme-Requisiten einfach in unseren Styles verwenden.

Zum Beispiel können wir die Farbe wie folgt auf unsere benutzerdefinierte bodyText -Farbe setzen.

 color: ${(props) => props.theme.bodyText};

Wir können die benutzerdefinierten Designs überall dort verwenden, wo wir Farbe in unserer Anwendung benötigen.

Um beispielsweise border-bottom zu definieren, gehen wir wie folgt vor.

 border-bottom: 2px solid ${(props) => props.theme.goldish};

Fazit

Wir begannen damit, uns mit Sanity.io zu befassen, es einzurichten und mit unserer React-Anwendung zu verbinden. Dann haben wir Redux eingerichtet und die GROQ-Sprache verwendet, um unsere API abzufragen. Wir haben gesehen, wie man Redux mithilfe von react-redux , „styled-components“ und „theming“ mit unserer React-App verbindet und verwendet.

Wir haben jedoch nur an der Oberfläche dessen gekratzt, was mit diesen Technologien möglich ist. Ich ermutige Sie, die Codebeispiele in meinem GitHub-Repo durchzugehen und sich an einem völlig anderen Projekt zu versuchen, das diese Technologien verwendet, um sie zu lernen und zu beherrschen.

Ressourcen

  • Sanity-Dokumentation
  • So erstellen Sie einen Blog mit Sanity.io von Kapehe
  • Redux-Dokumentation
  • Dokumentation zu gestylten Komponenten
  • GROQ-Spickzettel
  • Material-UI-Dokumentation
  • Redux-Middleware und SideEffects
  • Redux Thunk-Dokumentation