Come funzionano i riduttori Redux
Pubblicato: 2022-03-10state
, ti sarai sicuramente imbattuto in riduttori. Questo tutorial spiegherà il concetto di riduttori e come funzionano in modo specifico in Redux.In questo tutorial impareremo il concetto di riduttori e come funzionano, in particolare nelle applicazioni React. Per comprendere e utilizzare al meglio Redux, è essenziale una solida conoscenza dei riduttori. I riduttori forniscono un modo per aggiornare lo stato di un'applicazione utilizzando un'azione. È parte integrante della libreria Redux.
Questo tutorial è per gli sviluppatori che vogliono saperne di più su Redux Reducers. Sarebbe utile una comprensione di React e Redux. Alla fine del tutorial, dovresti avere una migliore comprensione del ruolo svolto dai riduttori in Redux. Scriveremo demo del codice e un'applicazione per comprendere meglio i riduttori e come influisce sullo stato di un'applicazione.
Che cos'è un riduttore
Un Reducer è una funzione pura che prende lo stato di un'applicazione e un'azione come argomenti e restituisce un nuovo stato. Ad esempio, un riduttore di autenticazione può assumere uno stato iniziale di un'applicazione sotto forma di un oggetto vuoto e un'azione che indica che un utente ha effettuato l'accesso e ha restituito un nuovo stato dell'applicazione con un utente connesso.
Le funzioni pure sono funzioni che non hanno effetti collaterali e restituiranno gli stessi risultati se vengono passati gli stessi argomenti.
Di seguito è riportato un esempio di una funzione pura:
const add = (x, y) => x + y; add(2, 5);
L'esempio sopra restituisce un valore basato sugli input, se passi 2
e 5
otterresti sempre 7
, purché sia lo stesso input nient'altro influisca sull'output che ottieni, questo è un esempio di una funzione pura.
Di seguito è riportato un esempio di una funzione di riduzione che accetta uno stato e un'azione.
const initialState = {}; const cartReducer = (state = initialState, action) => { // Do something here }
Definiamo i due parametri che assume un riduttore, state
e action
.
Stato
Uno stato è i dati con cui stanno lavorando i tuoi componenti: contiene i dati richiesti da un componente e determina ciò che un componente esegue il rendering. Una volta che un oggetto di state
cambia, il componente esegue nuovamente il rendering. Se lo stato di un'applicazione è gestito da Redux, il riduttore è il punto in cui si verificano i cambiamenti di stato.
Azione
Un'azione, è un oggetto che contiene il carico utile di informazioni. Sono l'unica fonte di informazioni per l'aggiornamento del negozio Redux. Store di aggiornamento dei riduttori in base al valore di action.type
. Qui definiremo action.type
come ADD_TO_CART
.
Secondo la documentazione ufficiale di Redux, le azioni sono le uniche cose che attivano le modifiche in un'applicazione Redux, contengono il carico utile per le modifiche a un negozio di applicazioni. Le azioni sono oggetti JavaScript che indicano a Redux il tipo di azione da eseguire, di solito sono definite come funzioni come quella di seguito:
const action = { type: 'ADD_TO_CART', payload: { product: 'margarine', quantity: 4 } }
Il codice sopra è un tipico valore di payload
che contiene ciò che un utente sta inviando e verrà utilizzato per aggiornare lo stato dell'applicazione. Come puoi vedere dall'alto, l'oggetto azione contiene il tipo di azione e un oggetto carico utile che sarebbero necessari per eseguire questa particolare azione.
Aggiornamento dello stato tramite riduttori
Per mostrare come funzionano i riduttori, diamo un'occhiata al contatore dei numeri di seguito:
const increaseAction = { type: 'INCREASE', }; const decreaseAction = { type: 'DECREASE' }; const countReducer = (state = 0, action) => { switch(action.type){ case INCREASE: return state + 1; case DECREASE : return state -1; default: return state; } };
Nel codice precedente, increaseAction
e decreaseAction
sono azioni utilizzate nel riduttore per determinare a cosa viene aggiornato lo state
. Successivamente, abbiamo una funzione di riduzione chiamata countReducer
, che accetta action
e uno state
iniziale il cui valore è 0
. Se il valore di action.type
è INCREASE
, restituiamo un nuovo stato che viene incrementato di 1, altrimenti se è DECREASE
viene restituito un nuovo stato che viene decrementato di 1. Nei casi in cui nessuna di queste condizioni è intesa, restituiamo state
.
Aggiornamento dello stato mediante riduttori: l'operatore di diffusione
Lo stato non può essere modificato direttamente, per creare o aggiornare lo stato, possiamo utilizzare l'operatore di diffusione JavaScript per assicurarci di non modificare direttamente il valore dello stato ma invece di restituire un nuovo oggetto che contiene uno stato passato ad esso e il carico utile dell'utente.
const contactAction = { type: 'GET_CONTACT', payload: ['0801234567', '0901234567'] }; const initialState = { contacts: [], contact: {}, }; export default function (state = initialState, action) { switch (action.type) { case GET_CONTACTS: return { ...state, contacts: action.payload, }; default: return state; }
Nel codice sopra, stiamo usando un operatore di diffusione per assicurarci di non modificare direttamente il valore dello stato, in questo modo possiamo restituire un nuovo oggetto che è riempito con lo stato che gli è passato e il carico utile inviato dal utente. Utilizzando un operatore di diffusione, possiamo assicurarci che lo stato rimanga lo stesso mentre aggiungiamo tutti i nuovi elementi e sostituiamo anche il campo dei contatti nello stato se era presente prima.
Riduttori Redux in azione: una demo
Per comprendere meglio i riduttori Redux e come funzionano, implementeremo una semplice app per la ricerca dei dettagli del film, il codice e la versione funzionante possono essere trovati qui su Codesandbox. Per iniziare, vai al tuo terminale e inizializza un'app di reazione usando il comando seguente:
create-react-app movie-detail-finder
Una volta inizializzato il nostro progetto, installiamo i pacchetti di cui avremmo bisogno per la nostra applicazione.
npm i axios reactstrap react-redux redux redux-thunk
Una volta installati i pacchetti, avviamo il nostro server di sviluppo utilizzando il comando:
npm start
Il comando sopra dovrebbe avviare il nostro server di sviluppo del progetto nel nostro browser. Quindi apriamo il nostro progetto nel nostro editor di testo preferito, all'interno della nostra cartella src
del progetto, elimina i seguenti file: App.css
, App.test.js
, serviceWorker.js
e setupTests.js
. Quindi, eliminiamo tutto il codice che fa riferimento ai file eliminati sul nostro App.js
.
Per questo progetto, utilizzeremo l'API Open Movie Database per ottenere le informazioni sui film, i contenuti e le immagini per la nostra applicazione, ecco un collegamento all'API, è necessario registrarsi e ottenere le chiavi di accesso per utilizzarlo per questo applicazione, una volta che hai finito, procediamo con la nostra applicazione costruendo i componenti.
Creazione di componenti di app
Innanzitutto, all'interno della nostra cartella src
nella nostra directory del progetto, crea una cartella chiamata componenti e all'interno della cartella, creiamo due cartelle chiamate Movie
e Searchbar
, il nostro componente dovrebbe assomigliare all'immagine seguente:
Componente del film di costruzione
Costruiamo il componente Movies
, che delineerà la struttura dei dettagli del film che otterremo dalla nostra API. Per fare ciò, all'interno della cartella Movies
del nostro componente, crea un nuovo file Movie.js
, quindi crea un componente basato sulla classe per i risultati dell'API, facciamolo di seguito.
import React, { Component } from 'react'; import { Card, CardImg, CardText, CardBody, ListGroup, ListGroupItem, Badge } from 'reactstrap'; import styles from './Movie.module.css'; class Movie extends Component{ render(){ if(this.props.movie){ return ( <div className={styles.Movie}> <h3 className="text-center my-4"> Movie Name: {this.props.movie.Title} </h3> <Card className="text-primary bg-dark"> <CardImg className={styles.Img} top src={this.props.movie.Poster} alt={this.props.movie.Title}/> <CardBody> <ListGroup className="bg-dark"> <ListGroupItem> <Badge color="primary">Actors:</Badge> {this.props.movie.Actors} </ListGroupItem> <ListGroupItem> <Badge color="primary">Genre:</Badge> {this.props.movie.Genre} </ListGroupItem> <ListGroupItem> <Badge color="primary">Year:</Badge> {this.props.movie.Year} </ListGroupItem> <ListGroupItem> <Badge color="primary">Writer(s):</Badge> {this.props.movie.Writer} </ListGroupItem> <ListGroupItem> <Badge color="primary">IMDB Rating:</Badge> {this.props.movie.imdbRating}/10 </ListGroupItem> </ListGroup> <CardText className="mt-3 text-white"> <Badge color="secondary">Plot:</Badge> {this.props.movie.Plot} </CardText> </CardBody> </Card> </div> ) } return null } } export default Movie;
Nel codice sopra, Utilizzo dei componenti del pacchetto reactstrap
, puoi consultare la documentazione qui. Abbiamo creato un componente Card che include il nome del film, l'immagine, il genere, l'attore, l'anno, l'autore del film, la valutazione e la trama. Per semplificare il passaggio dei dati da questo componente, abbiamo creato i dati in modo che fungano da supporto ad altri componenti. Quindi, costruiamo il nostro componente Searchbar
.
Costruire il nostro componente della barra di ricerca
Il nostro componente Searchbar
presenterà una barra di ricerca e un componente pulsante per la ricerca dei componenti del film, procediamo di seguito:
import React from 'react'; import styles from './Searchbar.module.css'; import { connect } from 'react-redux'; import { fetchMovie } from '../../actions'; import Movie from '../Movie/Movie'; class Searchbar extends React.Component{ render(){ return( <div className={styles.Form}> <div> <form onSubmit={this.formHandler}> <input type="text" placeholder="Movie Title" onChange={e => this.setState({title: e.target.value})} value={this.state.title}/> <button type="submit">Search</button> </form> </div> <Movie movie={this.props.movie}/> </div> ) } }
Nel codice sopra, stiamo importando connect
da react-redux
che viene utilizzato per connettere un componente React al negozio Redux, fornisce al componente informazioni dal negozio e fornisce anche funzioni utilizzate per inviare azioni al negozio. Successivamente, abbiamo importato il componente Movie
e una funzione fetchMovie
dalle azioni.
Successivamente, abbiamo un tag del modulo con una casella di input per inserire i titoli dei nostri film, utilizzando l'hook setState
di React, abbiamo aggiunto un evento e un valore onChange
che imposterà lo stato del title
sul valore inserito nella casella di input. Abbiamo un tag button
per cercare i titoli dei film e utilizzando il componente Movie
che abbiamo importato, abbiamo passato le proprietà del componente come props
al risultato della ricerca.
Il prossimo passo per noi è scrivere una funzione per inviare il titolo del nostro film all'API per inviarci risultati, dobbiamo anche impostare lo stato iniziale dell'applicazione. facciamolo qui sotto.
class Searchbar extends React.Component{ state = { title: '' } formHandler = (event) => { event.preventDefault(); this.props.fetchMovie(this.state.title); this.setState({title: ''}); }
Qui, abbiamo impostato lo stato iniziale dell'applicazione su stringhe vuote, abbiamo creato una funzione formHandler
che accetta un parametro di evento e passa la funzione fetchMovie
dall'azione e impostando il titolo come nuovo stato dell'applicazione. Per completare la nostra applicazione, esportiamo questo componente usando la proprietà connect di react-redux
, per fare ciò useremmo la proprietà react redux mapToStateProps
per selezionare la parte dei dati di cui il nostro componente avrebbe bisogno, puoi saperne di più su mapToStateProps
qui.
const mapStateToProps = (state) => { return { movie: state.movie } } export default connect(mapStateToProps, { fetchMovie })(Searchbar)
Aggiungiamo stili al nostro modulo creando un file Searchbar.module.css
e aggiungendo gli stili di seguito:
.Form{ margin: 3rem auto; width: 80%; height: 100%; } input{ display: block; height: 45px; border: none; width: 100%; border-radius: 0.5rem; outline: none; padding: 0 1rem; } input:focus, select:focus{ border: 2px rgb(16, 204, 179) solid; } .Form button{ display: block; background: rgb(16, 204, 179); padding: 0.7rem; border-radius: 0.5rem; width: 20%; margin-top: 0.7rem; color: #FFF; border: none; text-decoration: none; transition: all 0.5s; } button:hover{ opacity: 0.6; } @media(max-width: 700px){ input{ height: 40px; padding: 0 1rem; } .Form button{ width: 40%; padding: 0.6rem; } }
Dopo aver eseguito quanto sopra, il nostro componente della barra di ricerca dovrebbe essere simile all'immagine qui sotto:
Creazione di azioni per l'applicazione
In questo componente, imposteremo le azioni Redux per la nostra applicazione, in primo luogo, all'interno della directory src
, creeremo una cartella denominata actions
e all'interno della cartella, creeremo un file index.js
. Qui creiamo una funzione fetchMovie
che accetta un parametro title e recupera il filmato dall'API utilizzando Axios. Facciamolo qui sotto:
import axios from 'axios'; export const fetchMovie = (title) => async (dispatch) => { const response = await axios.get( `https://cors-anywhere.herokuapp.com/https://www.omdbapi.com/?t=${title}&apikey=APIKEY`); dispatch({ type: 'FETCH_MOVIE', payload: response.data }) }
Nel codice sopra, abbiamo importato axios
e creato una funzione chiamata fetchMovie
che accetta un parametro title
usando async/await in modo da poter fare una richiesta al server API. Abbiamo una funzione di dispatch
che invia a Redux l'oggetto azione che gli viene passato. Da quello che abbiamo sopra, stiamo inviando un'azione con il tipo FETCH_MOVIE
e il payload che contiene la risposta che abbiamo ottenuto dall'API.
NOTA: l' apikey
nella richiesta verrà sostituita con la propria apikey
dopo la registrazione a OmdbAPI .
Creazione di riduttori di app
In questa sezione creeremo dei riduttori per la nostra applicazione.
const fetchMovieReducer = (state = null, action) => { switch(action.type){ case 'FETCH_MOVIE': return action.payload; default: return state; } } const rootReducer = (state, action) => { return { movie: fetchMovieReducer(state, action) } } export default rootReducer;
Nel codice sopra, abbiamo creato un fetchMovieReducer
che assume uno stato predefinito di null
e un parametro action
, utilizzando un operatore switch, per il caso FETCH_MOVIE
restituiremo il valore di action.payload
che è il film che abbiamo ottenuto dall'API. Se l'azione che abbiamo provato a eseguire non è nel riduttore, restituiamo il nostro stato predefinito.
Successivamente, abbiamo creato una funzione rootReducer
che accetterà lo stato corrente e un'azione come input e restituirà fetchMovieReducer
.
Mettendolo insieme
In questa sezione, finiremo la nostra app creando il nostro redux store in index.js
, facciamolo di seguito:
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import App from './App'; import 'bootstrap/dist/css/bootstrap.min.css'; import './index.css'; import reducers from './reducers'; const store = createStore(reducers, applyMiddleware(thunk)) ReactDOM.render( <Provider store={store}> <> <App/> </> </Provider>, document.getElementById('root') )
Nel codice sopra, abbiamo creato l' store
applicazioni usando il metodo createStore
passando il riduttore che abbiamo creato e un middleware. I middleware sono componenti aggiuntivi che ci consentono di migliorare le funzionalità di Redux. Qui stiamo facendo uso del middleware Redux Thunk usando applyMiddleware
. Il middleware Redux Thunk è necessario affinché il nostro negozio esegua aggiornamenti asincroni. Questo è necessario perché per impostazione predefinita, Redux aggiorna lo store in modo sincrono.
Per assicurarci che la nostra applicazione conosca esattamente il negozio da utilizzare, abbiamo inserito la nostra applicazione in un componente Provider
e abbiamo passato il negozio come supporto, in questo modo altri componenti della nostra applicazione possono connettersi e condividere informazioni con il negozio.
Aggiungiamo un po' di stile al nostro file index.css
.
*{ margin: 0; padding: 0; box-sizing: border-box; } body{ background: rgb(15, 10, 34); color: #FFF; height: 100vh; max-width: 100%; }
Rendering e test di un cercatore di dettagli di film
In questa sezione, concluderemo la nostra applicazione eseguendo il rendering della nostra applicazione nel nostro App.js
, per fare ciò, creiamo un componente basato sulla classe chiamato App
e inizializziamo la nostra barra di ricerca e il campo di input.
import React from 'react'; import Searchbar from './components/Searchbar/Searchbar'; import styles from './App.module.css'; class App extends React.Component{ render(){ return( <div className={styles.App}> <h1 className={styles.Title}>Movies Search App</h1> <Searchbar/> </div> ) } } export default App;
Qui, abbiamo creato un componente basato sulla classe App con un h1
che dice App Ricerca film e aggiunto il nostro componente Searchbar
. La nostra applicazione dovrebbe apparire come l'immagine qui sotto:
Una demo funzionante è disponibile su Codesandbox.
Conclusione
I riduttori sono una parte importante della gestione dello stato Redux, con i riduttori possiamo scrivere funzioni pure per aggiornare aree specifiche delle nostre applicazioni Redux senza effetti collaterali. Abbiamo appreso le basi dei riduttori Redux, i loro usi e il concetto fondamentale di riduttori, stato e argomenti.
Puoi andare oltre vedendo la documentazione sui riduttori Redux qui. Puoi andare oltre e costruire di più sui riduttori Redux, fammi sapere cosa costruisci.
Risorse
- Documentazione React-Redux
- Documentazione Redux
-
connect()
funzione - applica la funzione
applyMiddleware