Creazione di un'app Web con React, Redux e Sanity.io
Pubblicato: 2022-03-10La rapida evoluzione delle piattaforme digitali ha posto seri limiti ai CMS tradizionali come Wordpress. Queste piattaforme sono accoppiate, non flessibili e focalizzate sul progetto, piuttosto che sul prodotto. Per fortuna, sono stati sviluppati diversi CMS headless per affrontare queste sfide e molte altre.
A differenza del CMS tradizionale, il CMS headless, che può essere descritto come Software as a Service (SaaS), può essere utilizzato per sviluppare siti Web, app mobili, display digitali e molti altri. Possono essere utilizzati su piattaforme illimitate. Se stai cercando un CMS indipendente dalla piattaforma, sviluppatore-first e che offra supporto multipiattaforma, non devi cercare più lontano dal CMS headless.
Un CMS senza testa è semplicemente un CMS senza testa. La head
qui si riferisce al frontend o al livello di presentazione mentre il body
si riferisce al backend o al repository di contenuti. Questo offre molti vantaggi interessanti. Ad esempio, consente allo sviluppatore di scegliere qualsiasi frontend a sua scelta e puoi anche progettare il livello di presentazione come desideri.
Ci sono molti CMS senza testa là fuori, alcuni dei più popolari includono Strapi, Contentful, Contentstack, Sanity, Butter CMS, Prismic, Storyblok, Directus, ecc. Questi CMS senza testa sono basati su API e hanno i loro punti di forza individuali. Ad esempio, CMS come Sanity, Strapi, Contentful e Storyblok sono gratuiti per piccoli progetti.
Questi CMS senza testa si basano anche su diversi stack tecnologici. Mentre Sanity.io è basato su React.js, Storyblok è basato su Vue.js. Come sviluppatore di React, questo è il motivo principale per cui ho subito scelto di interessarmi a Sanity. Tuttavia, essendo un CMS headless, ciascuna di queste piattaforme può essere collegata a qualsiasi frontend, sia Angular, Vue o React.
Ciascuno di questi CMS senza testa ha piani sia gratuiti che a pagamento che rappresentano un aumento significativo dei prezzi. Sebbene questi piani a pagamento offrano più funzionalità, non vorresti pagare così tanto per un progetto di piccole e medie dimensioni. Sanity cerca di risolvere questo problema introducendo opzioni di pagamento in base al consumo. Con queste opzioni, sarai in grado di pagare per ciò che usi ed evitare il salto di prezzo.
Un altro motivo per cui scelgo Sanity.io è il loro linguaggio GROQ. Per me, Sanity si distingue dalla massa offrendo questo strumento. Graphical-Relational Object Query (GROQ) riduce i tempi di sviluppo, ti aiuta a ottenere il contenuto di cui hai bisogno nella forma in cui ne hai bisogno e aiuta anche lo sviluppatore a creare un documento con un nuovo modello di contenuto senza modifiche al codice.
Inoltre, gli sviluppatori non sono vincolati al linguaggio GROQ. Puoi anche utilizzare GraphQL o anche il tradizionale axios
e fetch
nella tua app React per interrogare il back-end. Come la maggior parte degli altri CMS headless, Sanity ha una documentazione completa che contiene suggerimenti utili per costruire sulla piattaforma.
Nota: questo articolo richiede una conoscenza di base di React, Redux e CSS.
Iniziare con Sanity.io
Per utilizzare Sanity nella tua macchina, devi installare lo strumento Sanity CLI. Sebbene questo possa essere installato localmente sul tuo progetto, è preferibile installarlo a livello globale per renderlo accessibile a qualsiasi applicazione futura.
Per fare ciò, inserisci i seguenti comandi nel tuo terminale.
npm install -g @sanity/cli
Il flag -g
nel comando precedente abilita l'installazione globale.
Successivamente, dobbiamo inizializzare Sanity nella nostra applicazione. Sebbene questo possa essere installato come progetto separato, di solito è preferibile installarlo all'interno della tua app frontend (in questo caso React).
Nel suo blog, Kapehe ha spiegato in dettaglio come integrare Sanity con React. Sarà utile esaminare l'articolo prima di continuare con questo tutorial.
Inserisci i seguenti comandi per inizializzare Sanity nella tua app React.
sanity init
Il comando sanity
diventa disponibile quando abbiamo installato lo strumento Sanity CLI. Puoi visualizzare un elenco dei comandi Sanity disponibili digitando sanity
o Sanity sanity help
nel tuo terminale.
Durante la configurazione o l'inizializzazione del progetto, dovrai seguire le istruzioni per personalizzarlo. Ti verrà anche richiesto di creare un set di dati e puoi persino scegliere il loro set di dati personalizzato popolato con i dati. Per questa app di elenco, utilizzeremo il set di dati dei film di fantascienza personalizzati di Sanity. Questo ci eviterà di inserire i dati noi stessi.
Per visualizzare e modificare il tuo set di dati, cd
nella sottodirectory Sanity nel tuo terminale e inserisci sanity start
. Di solito viene eseguito su https://localhost:3333/
. Potrebbe essere necessario effettuare il login per accedere all'interfaccia (assicurati di accedere con lo stesso account utilizzato durante l'inizializzazione del progetto). Di seguito viene mostrata una schermata dell'ambiente.
Sanity-React Comunicazione bidirezionale
Sanity e React devono comunicare tra loro per un'applicazione completamente funzionale.
Impostazione delle origini CORS in Sanity Manager
Per prima cosa collegheremo la nostra app React a Sanity. Per fare ciò, accedi a https://manage.sanity.io/
e individua CORS origins
in API Settings
nella scheda Settings
. Qui, dovrai collegare la tua origine front-end al back-end Sanity. La nostra app React funziona su https://localhost:3000/
per impostazione predefinita, quindi dobbiamo aggiungerla al CORS.
Questo è mostrato nella figura seguente.
Collegare la sanità mentale per reagire
Sanity associa un project ID
a ogni progetto che crei. Questo ID è necessario quando lo colleghi alla tua applicazione frontend. Puoi trovare l'ID del progetto nel tuo Sanity Manager.
Il backend comunica con React utilizzando una libreria nota come sanity client
. Devi installare questa libreria nel tuo progetto Sanity inserendo i seguenti comandi.
npm install @sanity/client
Crea un file sanitySetup.js
(il nome del file non ha importanza), nella cartella src
del tuo progetto e inserisci i seguenti codici React per stabilire una connessione tra Sanity e React.
import sanityClient from "@sanity/client" export default sanityClient({ projectId: PROJECT_ID, dataset: DATASET_NAME, useCdn: true });
Abbiamo passato il nostro projectId
, il dataset name
del set di dati e un useCdn
booleano all'istanza del client di sanity importato da @sanity/client
. Questo fa la magia e collega la nostra app al back-end.
Ora che abbiamo completato la connessione a due vie, entriamo subito nella costruzione del nostro progetto.
Configurazione e connessione di Redux alla nostra app
Avremo bisogno di alcune dipendenze per lavorare con Redux nella nostra app React. Apri il tuo terminale nel tuo ambiente React e inserisci i seguenti comandi bash.
npm install redux react-redux redux-thunk
Redux è una libreria di gestione dello stato globale che può essere utilizzata con la maggior parte dei framework e librerie frontend come React. Tuttavia, abbiamo bisogno di uno strumento intermedio react-redux
per abilitare la comunicazione tra il nostro negozio Redux e la nostra applicazione React. Redux thunk ci aiuterà a restituire una funzione invece di un oggetto azione da Redux.
Mentre potremmo scrivere l'intero flusso di lavoro Redux in un file, spesso è più ordinato e meglio separare le nostre preoccupazioni. Per questo, divideremo il nostro flusso di lavoro in tre file: actions
, reducers
e quindi store
. Tuttavia, abbiamo anche bisogno di un file separato per memorizzare i action types
, noti anche come constants
.
Allestimento del negozio
Il negozio è il file più importante in Redux. Organizza e imballa gli stati e li spedisce alla nostra applicazione React.
Ecco la configurazione iniziale del nostro negozio Redux necessaria per connettere il nostro flusso di lavoro Redux.
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/"; export default createStore( reducers, applyMiddleware(thunk) );
La funzione createStore
in questo file accetta tre parametri: il reducer
(obbligatorio), lo stato iniziale e l'enhancer (solitamente un middleware, in questo caso, thunk
fornito tramite applyMiddleware
). I nostri riduttori verranno archiviati in una cartella reducers
e li combineremo ed esporteremo in un file index.js
nella cartella reducers
. Questo è il file che abbiamo importato nel codice sopra. Rivedremo questo file più tardi.
Introduzione al linguaggio GROQ di Sanity
Sanity fa un ulteriore passo avanti nelle query sui dati JSON introducendo GROQ. GROQ sta per Graph-Relational Object Query. Secondo Sanity.io, GROQ è un linguaggio di query dichiarativo progettato per interrogare raccolte di documenti JSON in gran parte privi di schema.
Sanity fornisce anche GROQ Playground per aiutare gli sviluppatori a familiarizzare con il linguaggio. Tuttavia, per accedere al parco giochi, è necessario installare Sanity Vision . Esegui sanity install @sanity/vision
sul tuo terminale per installarlo.
GROQ ha una sintassi simile a GraphQL ma è più condensato e più facile da leggere. Inoltre, a differenza di GraphQL, GROQ può essere utilizzato per interrogare dati JSON.
Ad esempio, per recuperare ogni elemento nel nostro documento filmato, utilizzeremo la seguente sintassi GROQ.
*[_type == "movie"]
Tuttavia, se desideriamo recuperare solo gli _ids
e i membri crewMembers
nel nostro documento del filmato. Dobbiamo specificare quei campi come segue.
`*[_type == 'movie']{ _id, crewMembers }
Qui, abbiamo usato *
per dire a GROQ che vogliamo ogni documento di _type
movie. _type
è un attributo nella raccolta di film. Possiamo anche restituire il tipo come abbiamo fatto _id
e crewMembers
come segue:
*[_type == 'movie']{ _id, _type, crewMembers }
Lavoreremo di più su GROQ implementandolo nelle nostre azioni Redux, ma puoi controllare la documentazione di Sanity.io per GROQ per saperne di più. Il cheat sheet delle query GROQ fornisce molti esempi per aiutarti a padroneggiare il linguaggio di query.
Impostazione delle costanti
Abbiamo bisogno di costanti per tenere traccia dei tipi di azione in ogni fase del flusso di lavoro Redux. Le costanti aiutano a determinare il tipo di azione inviata in ogni momento. Ad esempio, possiamo monitorare quando l'API viene caricata, caricata completamente e quando si verifica un errore.
Non è necessario definire le costanti in un file separato, ma per semplicità e chiarezza, questa è solitamente la migliore pratica in Redux.
Per convenzione, le costanti in Javascript sono definite con lettere maiuscole. Seguiremo le migliori pratiche qui per definire le nostre costanti. Ecco un esempio di una costante per indicare le richieste per il recupero di filmati in movimento.
export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST";
Qui, abbiamo creato una costante MOVIE_FETCH_REQUEST
che denota un tipo di azione di MOVIE_FETCH_REQUEST
. Questo ci aiuta a chiamare facilmente questo tipo di azione senza usare strings
ed evitare bug. Abbiamo anche esportato la costante per essere disponibile ovunque nel nostro progetto.
Allo stesso modo, possiamo creare altre costanti per il recupero dei tipi di azione che indicano quando la richiesta ha esito positivo o negativo. Un codice completo per movieConstants.js
è fornito nel codice seguente.
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";
Qui abbiamo definito diverse costanti per il recupero di un film o un elenco di film, l'ordinamento e il recupero dei film più popolari. Si noti che impostiamo costanti per determinare quando la richiesta viene loading
, successful
e failed
.
Allo stesso modo, il nostro file personConstants.js
è riportato di seguito:
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";
Come movieConstants.js
, impostiamo un elenco di costanti per il recupero di una o più persone. Abbiamo anche impostato una costante per il conteggio delle persone. Le costanti seguono la convenzione descritta per movieConstants.js
e le abbiamo anche esportate per renderle accessibili ad altre parti della nostra applicazione.
Infine, implementeremo la modalità chiara e scura nell'app e quindi avremo un altro file di costanti globalConstants.js
. Diamo un'occhiata.
export const SET_LIGHT_THEME = "SET_LIGHT_THEME"; export const SET_DARK_THEME = "SET_DARK_THEME";
Qui impostiamo le costanti per determinare quando viene inviata la modalità chiara o scura. SET_LIGHT_THEME
determina quando l'utente passa al tema chiaro e SET_DARK_THEME
determina quando viene selezionato il tema scuro. Abbiamo anche esportato le nostre costanti come mostrato.
Impostazione delle azioni
Per convenzione, le nostre azioni sono archiviate in una cartella separata. Le azioni sono raggruppate in base al tipo. Ad esempio, le nostre azioni film sono archiviate in movieActions.js
mentre le nostre azioni persona sono archiviate nel file personActions.js
.
Abbiamo anche globalActions.js
per occuparci di alternare il tema dalla modalità chiara a quella scura.
Recuperiamo tutti i film 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 }); } };
Ricordi quando abbiamo creato il file sanitySetup.js
per connettere React al nostro backend Sanity? Qui, abbiamo importato la configurazione per consentirci di interrogare il nostro backend di sanità mentale utilizzando GROQ. Abbiamo anche importato alcune costanti esportate dal file movieConstants.js
nella cartella delle constants
.
Successivamente, abbiamo creato la funzione di azione fetchAllMovies
per recuperare tutti i film della nostra raccolta. La maggior parte delle applicazioni React tradizionali utilizza axios
o fetch
per recuperare i dati dal back-end. Ma mentre potremmo usare qualcuno di questi qui, stiamo usando GROQ
di Sanity. Per entrare nella modalità GROQ
, dobbiamo chiamare la funzione sanityAPI.fetch()
come mostrato nel codice sopra. Qui, sanityAPI
è la connessione React-Sanity che abbiamo impostato in precedenza. Questo restituisce una Promise
e quindi deve essere chiamato in modo asincrono. Abbiamo usato la sintassi async-await
qui, ma possiamo anche usare la sintassi .then
.
Poiché stiamo usando thunk
nella nostra applicazione, possiamo restituire una funzione invece di un oggetto azione. Tuttavia, abbiamo scelto di trasmettere la dichiarazione di reso in una riga.
const fetchAllMovies = () => async (dispatch) => { ... }
Nota che possiamo anche scrivere la funzione in questo modo:
const fetchAllMovies = () => { return async (dispatch)=>{ ... } }
In generale, per recuperare tutti i film, abbiamo prima inviato un tipo di azione che tiene traccia quando la richiesta è ancora in caricamento. Abbiamo quindi utilizzato la sintassi GROQ di Sanity per interrogare in modo asincrono il documento del film. Abbiamo recuperato _id
e l'URL del poster dei dati del film. Abbiamo quindi restituito un payload contenente i dati ottenuti dall'API.
Allo stesso modo, possiamo recuperare i film in base al loro _id
, ordinare i film e ottenere i film più popolari.
Possiamo anche recuperare film che corrispondono al riferimento di una determinata persona. Lo abbiamo fatto nella funzione 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 }); } };
Questa funzione accetta un argomento e controlla se person._ref
in castMembers
o crewMembers
corrisponde all'argomento passato. Restituiamo il film _id
, poster url
e title
insieme. Inviamo anche un'azione di tipo MOVIES_REF_FETCH_SUCCESS
, allegando un payload dei dati restituiti e, se si verifica un errore, inviamo un'azione di tipo MOVIE_REF_FETCH_FAIL
, allegando un payload del messaggio di errore, grazie al wrapper try-catch
.
Nella funzione fetchMovieById
, abbiamo utilizzato GROQ
per recuperare un filmato che corrisponde a un particolare id
passato alla funzione.
La sintassi GROQ
per la funzione è mostrata di seguito.
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]` );
Come per l'azione fetchAllMovies
, abbiamo iniziato selezionando tutti i documenti di tipo movie
ma siamo andati oltre selezionando solo quelli con un ID fornito alla funzione. Dal momento che intendiamo mostrare molti dettagli per il film, abbiamo specificato una serie di attributi da recuperare.
Abbiamo recuperato l' id
del film e anche alcuni attributi nell'array castMembers
, vale a dire ref
, characterName
, il nome della persona e l'immagine della persona. Abbiamo anche cambiato l'alias da castMembers
a cast
.
Come il castMembers
, abbiamo selezionato alcuni attributi dall'array crewMembers
, vale a dire ref
, department
, job
, il nome della persona e l'immagine della persona. abbiamo anche cambiato l'alias da crewMembers
a crew
.
Allo stesso modo, abbiamo selezionato il testo della panoramica, la popolarità, l'URL della locandina del film, la data di uscita e il titolo del film.
Il linguaggio GROQ di Sanity ci consente anche di ordinare un documento. Per ordinare un articolo, passiamo l' ordine accanto a un operatore pipe .
Ad esempio, se desideriamo ordinare i film in base alla data di releaseDate
in ordine crescente, potremmo fare quanto segue.
const data = await sanityAPI.fetch( `*[_type == 'movie']{ ... } | order(releaseDate, asc)` );
Abbiamo usato questa nozione nella funzione sortMoviesBy
per ordinare in ordine crescente o decrescente.
Diamo un'occhiata a questa funzione di seguito.
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 }); } };
Abbiamo iniziato inviando un'azione di tipo MOVIES_SORT_REQUEST
per determinare quando la richiesta viene caricata. Abbiamo quindi utilizzato la sintassi GROQ
per ordinare e recuperare i dati dalla raccolta di movie
. L'elemento da ordinare è fornito nella variabile item
e la modalità di ordinamento (crescente o decrescente) è fornita nella variabile type
. Di conseguenza, abbiamo restituito l' id
, l'URL del poster e il titolo. Una volta che i dati sono stati restituiti, abbiamo inviato un'azione di tipo MOVIES_SORT_SUCCESS
e se fallisce, inviamo un'azione di tipo MOVIES_SORT_FAIL
.
Un concetto GROQ
simile si applica alla funzione getMostPopular
. La sintassi GROQ
è mostrata di seguito.
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]` );
L'unica differenza qui è che abbiamo ordinato i film in base alla popolarità in ordine decrescente e quindi abbiamo selezionato solo i primi tre. Gli elementi vengono restituiti in un indice a base zero e quindi i primi tre elementi sono gli elementi 0, 1 e 2. Se desideriamo recuperare i primi dieci elementi, possiamo passare [0..9]
alla funzione.
Ecco il codice completo per le azioni del filmato nel file 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 };
Installazione dei riduttori
I riduttori sono uno dei concetti più importanti in Redux. Prendono lo stato precedente e determinano i cambiamenti di stato.
In genere, utilizzeremo l'istruzione switch per eseguire una condizione per ogni tipo di azione. Ad esempio, possiamo restituire il loading
quando il tipo di azione indica il caricamento e quindi il carico utile quando indica il successo o l'errore. Ci si aspetta che prenda come argomenti lo initial state
e l' action
.
Il nostro file movieReducers.js
contiene vari riduttori per abbinare le azioni definite nel file movieActions.js
. Tuttavia, ciascuno dei riduttori ha una sintassi e una struttura simili. Le uniche differenze sono le constants
che chiamano e i valori che restituiscono.
Iniziamo dando un'occhiata al fetchAllMoviesReducer
nel file movieReducers.js
.
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; } };
Come tutti i riduttori, fetchAllMoviesReducer
prende l'oggetto dello stato iniziale ( state
) e l'oggetto action
come argomenti. Abbiamo usato l'istruzione switch per controllare i tipi di azione in ogni momento. Se corrisponde a MOVIES_FETCH_REQUEST
, restituiamo il caricamento come true per consentirci di mostrare un indicatore di caricamento all'utente.
Se corrisponde a MOVIES_FETCH_SUCCESS
, disattiviamo l'indicatore di caricamento e quindi restituiamo il payload dell'azione in una variabile movies
. Ma se è MOVIES_FETCH_FAIL
, disattiviamo anche il caricamento e quindi restituiamo l'errore. Vogliamo anche l'opzione per ripristinare i nostri filmati. Questo ci consentirà di ripulire gli stati quando sarà necessario.
Abbiamo la stessa struttura per altri riduttori. Il movieReducers.js
completo è mostrato di seguito.
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 };
Abbiamo anche seguito la stessa identica struttura per personReducers.js
. Ad esempio, la funzione fetchAllPersonsReducer
definisce gli stati per il recupero di tutte le persone nel database.
Questo è indicato nel codice seguente.
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; } };
Proprio come fetchAllMoviesReducer
, abbiamo definito fetchAllPersonsReducer
con state
e action
come argomenti. Si tratta di una configurazione standard per i riduttori Redux. Abbiamo quindi utilizzato l'istruzione switch per controllare i tipi di azione e se è di tipo PERSONS_FETCH_REQUEST
, restituiamo il caricamento come true. Se è PERSONS_FETCH_SUCCESS
, disattiviamo il caricamento e restituiamo il payload, e se è PERSONS_FETCH_FAIL
, restituiamo l'errore.
Combinazione di riduttori
La funzione combineReducers
di Redux ci consente di combinare più di un riduttore e passarlo al negozio. Uniremo i nostri film e persone riduttori in un file index.js
all'interno della cartella reducers
.
Diamo un'occhiata.
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 });
Qui abbiamo importato tutti i riduttori dal file di film, persone e riduttori globali e li abbiamo passati alla funzione combineReducers
. La funzione combineReducers
prende un oggetto che ci permette di passare tutti i nostri riduttori. Possiamo anche aggiungere un alias agli argomenti nel processo.
Lavoreremo sui globalReducers
in seguito.
Ora possiamo passare i riduttori nel file store.js
. Questo è mostrato di seguito.
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/index"; export default createStore(reducers, initialState, applyMiddleware(thunk));
Dopo aver impostato il nostro flusso di lavoro Redux, impostiamo la nostra applicazione React.
Configurazione della nostra applicazione React
La nostra applicazione di reazione elencherà i film e i membri del cast e della troupe corrispondenti. Useremo react-router-dom
per il routing e styled-components
per lo stile dell'app. Utilizzeremo anche l'interfaccia utente materiale per le icone e alcuni componenti dell'interfaccia utente.
Immettere il seguente comando bash
per installare le dipendenze.
npm install react-router-dom @material-ui/core @material-ui/icons query-string
Ecco cosa costruiremo:
Collegamento di Redux alla nostra app React
React-redux
viene fornito con una funzione Provider che ci consente di connettere la nostra applicazione allo store Redux. Per fare ciò, dobbiamo passare un'istanza dello store al Provider. Possiamo farlo nel nostro index.js
o App.js
Ecco il nostro file index.js.
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") );
Qui, abbiamo importato Provider
da react-redux
e store
dal nostro negozio Redux. Quindi abbiamo avvolto l'intero albero dei componenti con il Provider, passandogli il negozio.
Successivamente, abbiamo bisogno react-router-dom
per l'instradamento nella nostra applicazione React. react-router-dom
viene fornito con BrowserRouter
, Switch
e Route
che possono essere utilizzati per definire il nostro percorso e percorsi.
Lo facciamo nel nostro file App.js
Questo è mostrato di seguito.
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;
Questa è una configurazione standard per il routing con react-router-dom. Puoi verificarlo nella loro documentazione. Abbiamo importato i nostri componenti Header
, Footer
, PersonsList
e MovieList
. Abbiamo quindi impostato il react-router-dom
avvolgendo tutto in Router
e Switch
.
Poiché vogliamo che le nostre pagine condividano la stessa intestazione e piè di pagina, abbiamo dovuto passare il componente <Header />
e <Footer />
prima di eseguire il wrapping della struttura con Switch
. Abbiamo anche fatto una cosa simile con l'elemento main
poiché vogliamo che avvolga l'intera applicazione.
Abbiamo passato ogni componente al percorso usando Route
from react-router-dom
.
Definire le nostre pagine e componenti
La nostra applicazione è organizzata in modo strutturato. I componenti riutilizzabili sono archiviati nella cartella dei components
mentre le pagine sono archiviate nella cartella delle pages
.
Le nostre pages
comprendono movieListPage.js
, moviePage.js
, PersonListPage.js
e PersonPage.js
. MovieListPage.js
elenca tutti i film nel nostro backend Sanity.io così come i film più popolari.
Per elencare tutti i film, dispatch
semplicemente l'azione fetchAllMovies
definita nel nostro file movieAction.js
. Dal momento che dobbiamo recuperare l'elenco non appena la pagina viene caricata, dobbiamo definirlo in useEffect
. Questo è mostrato di seguito.
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;
Grazie agli useDispatch
e useSelector
, possiamo inviare azioni Redux e selezionare gli stati appropriati dallo store Redux. Si noti che gli stati loading
, error
e movies
sono stati definiti nelle nostre funzioni di Reducer e qui li abbiamo selezionati utilizzando l' useSelector
di React Redux. Questi stati, vale a dire loading
, error
e movies
, diventano disponibili immediatamente dopo aver inviato le azioni fetchAllMovies()
.
Una volta ottenuto l'elenco dei film, possiamo visualizzarlo nella nostra applicazione utilizzando la funzione map
o come desideriamo.
Ecco il codice completo per il file 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
Abbiamo iniziato inviando l'azione getMostPopular
movies (questa azione seleziona i film con la più alta popolarità) nel gancio useEffect
. Questo ci consente di recuperare i film più popolari non appena la pagina viene caricata. Inoltre, abbiamo consentito agli utenti di ordinare i film in base alla data di releaseDate
e alla popularity
. Questo è gestito dall'azione sortMoviesBy
inviata nel codice sopra. Inoltre, abbiamo inviato i fetchAllMovies
in base ai parametri della query.
Inoltre, abbiamo utilizzato il gancio useSelector
per selezionare i riduttori corrispondenti per ciascuna di queste azioni. Abbiamo selezionato gli stati per il loading
, l' error
e i movies
per ciascuno dei riduttori.
Dopo aver ottenuto i movies
dai riduttori, ora possiamo mostrarli all'utente. Qui, abbiamo usato la funzione map
ES6 per farlo. Per prima cosa abbiamo visualizzato un caricatore ogni volta che viene caricato ciascuno degli stati del film e, se si verifica un errore, viene visualizzato il messaggio di errore. Infine, se otteniamo un filmato, mostriamo l'immagine del filmato all'utente utilizzando la funzione map
. Abbiamo racchiuso l'intero componente in un componente MovieListContainer
.
Il <MovieListContainer> … </MovieListContainer>
è un div
definito utilizzando componenti con stile. Presto ne daremo una breve occhiata.
Styling della nostra app con componenti in stile
I componenti stilizzati ci consentono di applicare uno stile alle nostre pagine e ai componenti su base individuale. Offre anche alcune caratteristiche interessanti come l' inheritance
, il Theming
, il passing of props
, ecc.
Sebbene desideriamo sempre dare uno stile alle nostre pagine su base individuale, a volte può essere desiderabile uno stile globale. È interessante notare che i componenti con stile forniscono un modo per farlo, grazie alla funzione createGlobalStyle
.
Per utilizzare i componenti in stile nella nostra applicazione, dobbiamo installarla. Apri il tuo terminale nel tuo progetto react e inserisci il seguente comando bash
.
npm install styled-components
Dopo aver installato i componenti con stile, iniziamo con i nostri stili globali.
Creiamo una cartella separata nella nostra directory src
denominata styles
. Questo memorizzerà tutti i nostri stili. Creiamo anche un file globalStyles.js
all'interno della cartella degli stili. Per creare uno stile globale nei componenti con stile, è necessario importare createGlobalStyle
.
import { createGlobalStyle } from "styled-components";
Possiamo quindi definire i nostri stili come segue:
export const GlobalStyle = createGlobalStyle` ... `
I componenti stilizzati utilizzano il modello letterale per definire gli oggetti di scena. All'interno di questo letterale, possiamo scrivere i nostri codici CSS
tradizionali.
Abbiamo anche importato deviceWidth
definito in un file chiamato definition.js
. deviceWidth
contiene la definizione dei punti di interruzione per l'impostazione delle nostre query multimediali.
import { deviceWidth } from "./definition";
Abbiamo impostato l'overflow su nascosto per controllare il flusso della nostra applicazione.
html, body{ overflow-x: hidden; }
Abbiamo anche definito lo stile dell'intestazione utilizzando il selettore di stile .header
.
.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%; } ... }
Qui vengono definiti vari stili come il colore di sfondo, z-index, padding e molte altre proprietà CSS tradizionali.
Abbiamo usato gli props
di scena dei componenti di stile per impostare il colore di sfondo. Questo ci permette di impostare variabili dinamiche che possono essere passate dal nostro componente. Inoltre, abbiamo anche passato la variabile del tema per consentirci di sfruttare al meglio la commutazione del nostro tema.
Il tema è possibile qui perché abbiamo avvolto la nostra intera applicazione con ThemeProvider
da componenti in stile. Ne parleremo tra un momento. Inoltre, abbiamo utilizzato la CSS flexbox
per definire correttamente lo stile della nostra intestazione e impostare la posizione su fixed
per assicurarci che rimanga fissa rispetto al browser. Abbiamo anche definito i punti di interruzione per rendere le intestazioni mobile friendly.
Ecco il codice completo per il nostro file globalStyles.js
.
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}; } `;
Si noti che abbiamo scritto puro codice CSS all'interno del letterale, ma ci sono alcune eccezioni. I componenti stilizzati ci consentono di passare oggetti di scena. Puoi saperne di più su questo nella documentazione.
Oltre a definire stili globali, possiamo definire stili per singole pagine.
Ad esempio, ecco lo stile per PersonListPage.js
definito in PersonStyle.js
nella cartella degli styles
.
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; } } } } `;
Per prima cosa abbiamo importato styled
da styled-components
e deviceWidth
dal file di definition
. Abbiamo quindi definito PersonsListContainer
come un div
per contenere i nostri stili. Utilizzando le query multimediali e i punti di interruzione stabiliti, abbiamo reso la pagina ottimizzata per i dispositivi mobili impostando vari punti di interruzione.
In questo caso, abbiamo utilizzato solo i punti di interruzione del browser standard per schermi piccoli, grandi e molto grandi. Abbiamo anche sfruttato al meglio la flexbox e la griglia CSS per definire e visualizzare correttamente i nostri contenuti sulla pagina.
Per utilizzare questo stile nel nostro file PersonListPage.js
, lo abbiamo semplicemente importato e aggiunto alla nostra pagina come segue.
import React from "react"; const PersonsListPage = () => { return ( <PersonsListContainer> ... </PersonsListContainer> ); }; export default PersonsListPage;
Il wrapper genererà un div
perché lo abbiamo definito come div nei nostri stili.
Aggiungere temi e concludere
È sempre una caratteristica interessante aggiungere temi alla nostra applicazione. Per questo, abbiamo bisogno di quanto segue:
- I nostri temi personalizzati definiti in un file separato (nel nostro caso file
definition.js
). - La logica definita nelle nostre azioni e riduttori Redux.
- Chiamando il nostro tema nella nostra applicazione e passandolo attraverso l'albero dei componenti.
Diamo un'occhiata.
Ecco il nostro oggetto theme
nel file 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)" } };
Abbiamo aggiunto varie proprietà di colore per i temi chiari e scuri. I colori sono scelti con cura per consentire la visibilità sia in modalità chiara che scura. Puoi definire i tuoi temi come vuoi. Questa non è una regola rigida.
Successivamente, aggiungiamo la funzionalità a Redux.
Abbiamo creato globalActions.js
nella nostra cartella delle azioni Redux e aggiunto i seguenti codici.
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)); };
Qui, abbiamo semplicemente importato i nostri temi definiti. Inviato le azioni corrispondenti, passando il carico utile dei temi di cui avevamo bisogno. I risultati del carico utile vengono archiviati nella memoria locale utilizzando le stesse chiavi sia per i temi chiari che per quelli scuri. Questo ci consente di mantenere gli stati nel browser.
Dobbiamo anche definire il nostro riduttore per i temi.
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; } };
Questo è molto simile a quello che abbiamo fatto. Abbiamo utilizzato l'istruzione switch
per verificare il tipo di azione e quindi restituito il payload
appropriato. Abbiamo anche restituito una light
di stato che determina se il tema chiaro o scuro è selezionato dall'utente. Lo useremo nei nostri componenti.
Dobbiamo anche aggiungerlo al nostro riduttore di radici e archiviare. Ecco il codice completo per il nostro 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));
Poiché dovevamo persistere il tema quando l'utente si aggiornava, dovevamo recuperarlo dalla memoria locale usando localStorage.getItem()
e passarlo al nostro stato iniziale.
Aggiunta della funzionalità alla nostra applicazione React
I componenti con stile ci forniscono ThemeProvider
che ci consente di passare i temi attraverso la nostra applicazione. Possiamo modificare il nostro file App.js per aggiungere questa funzionalità.
Diamo un'occhiata.
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;
Passando i temi attraverso ThemeProvider
, possiamo facilmente utilizzare gli oggetti di scena nei nostri stili.
Ad esempio, possiamo impostare il colore sul nostro colore personalizzato bodyText
come segue.
color: ${(props) => props.theme.bodyText};
Possiamo usare i temi personalizzati ovunque abbiamo bisogno del colore nella nostra applicazione.
Ad esempio, per definire border-bottom
, procediamo come segue.
border-bottom: 2px solid ${(props) => props.theme.goldish};
Conclusione
Abbiamo iniziato esplorando Sanity.io, configurandolo e collegandolo alla nostra applicazione React. Quindi abbiamo configurato Redux e utilizzato il linguaggio GROQ per interrogare la nostra API. Abbiamo visto come connettere e utilizzare Redux alla nostra app React utilizzando react-redux
, utilizzare componenti di stile e temi.
Tuttavia, abbiamo solo scalfito la superficie su ciò che è possibile con queste tecnologie. Ti incoraggio a esaminare gli esempi di codice nel mio repository GitHub e provare a realizzare un progetto completamente diverso utilizzando queste tecnologie per impararle e padroneggiarle.
Risorse
- Documentazione sulla sanità mentale
- Come creare un blog con Sanity.io di Kapehe
- Documentazione Redux
- Documentazione sui componenti con stile
- Foglio informativo di GROQ
- Documentazione materiale dell'interfaccia utente
- Middleware Redux ed effetti collaterali
- Redux Thunk Documentazione