Erstellen einer Web-App mit React, Redux und Sanity.io
Veröffentlicht: 2022-03-10Die 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.
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-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.
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