Ganci React utili che puoi usare nei tuoi progetti
Pubblicato: 2022-03-10Gli hook sono semplicemente funzioni che ti consentono di collegarti o utilizzare le funzionalità di React. Sono stati presentati alla React Conf 2018 per affrontare tre problemi principali dei componenti di classe: l'inferno del wrapper, i componenti enormi e le classi confuse. Gli hook danno potenza ai componenti funzionali di React, rendendo possibile lo sviluppo di un'intera applicazione con esso.
I suddetti problemi dei componenti di classe sono connessi e risolverli l'uno senza l'altro potrebbe introdurre ulteriori problemi. Per fortuna, gli hook hanno risolto tutti i problemi in modo semplice ed efficiente, creando spazio per funzionalità più interessanti in React. Gli hook non sostituiscono concetti e classi React già esistenti, forniscono semplicemente un'API per accedervi direttamente.
Il team di React ha introdotto diversi hook in React 16.8. Tuttavia, puoi anche utilizzare hook di fornitori di terze parti nella tua applicazione o persino creare un hook personalizzato. In questo tutorial, daremo un'occhiata ad alcuni hook utili in React e come usarli. Esamineremo diversi esempi di codice di ciascun hook ed esploreremo anche come creare un hook personalizzato.
Nota: questo tutorial richiede una conoscenza di base di Javascript (ES6+) e React.
Motivazione dietro i ganci
Come affermato in precedenza, gli hook sono stati creati per risolvere tre problemi: l'inferno del wrapper, componenti enormi e classi confuse. Diamo un'occhiata a ciascuno di questi in modo più dettagliato.
Involucro dell'inferno
Le applicazioni complesse create con componenti di classe finiscono facilmente nell'inferno del wrapper. Se esamini l'applicazione in React Dev Tools, noterai componenti profondamente nidificati. Ciò rende molto difficile lavorare con i componenti o eseguirne il debug. Sebbene questi problemi possano essere risolti con componenti di ordine superiore e oggetti di rendering , richiedono che tu modifichi un po' il tuo codice. Ciò potrebbe creare confusione in un'applicazione complessa.
Gli hook sono facili da condividere, non devi modificare i tuoi componenti prima di riutilizzare la logica.
Un buon esempio di ciò è l'uso del Redux connect
Higher Order Component (HOC) per iscriversi al negozio Redux. Come tutti gli HOC, per utilizzare connect HOC, è necessario esportare il componente insieme alle funzioni di ordine superiore definite. Nel caso di connect
, avremo qualcosa di questo modulo.
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
Dove mapStateToProps
e mapDispatchToProps
sono funzioni da definire.
Mentre nell'era Hooks, si può facilmente ottenere lo stesso risultato in modo ordinato e succinto usando gli hook Redux useSelector
e useDispatch
.
Componenti enormi
I componenti della classe di solito contengono effetti collaterali e logica stateful. Man mano che l'applicazione cresce in complessità, è comune che il componente diventi disordinato e confuso. Questo perché ci si aspetta che gli effetti collaterali siano organizzati dai metodi del ciclo di vita piuttosto che dalla funzionalità. Sebbene sia possibile dividere i componenti e renderli più semplici, ciò spesso introduce un livello di astrazione più elevato.
I ganci organizzano gli effetti collaterali in base alla funzionalità ed è possibile dividere un componente in pezzi in base alla funzionalità.
Classi confuse
Le classi sono generalmente un concetto più difficile delle funzioni. I componenti basati sulla classe React sono dettagliati e un po' difficili per i principianti. Se non conosci Javascript, potresti trovare funzioni più facili con cui iniziare a causa della loro sintassi leggera rispetto alle classi. La sintassi potrebbe creare confusione; a volte, è possibile dimenticare di associare un gestore di eventi che potrebbe rompere il codice.
React risolve questo problema con componenti funzionali e hook, consentendo agli sviluppatori di concentrarsi sul progetto piuttosto che sulla sintassi del codice.
Ad esempio, i seguenti due componenti React produrranno esattamente lo stesso risultato.
import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { num: 0 }; this.incrementNumber = this.incrementNumber.bind(this); } incrementNumber() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <h1>{this.state.num}</h1> <button onClick={this.incrementNumber}>Increment</button> </div> ); } }
import React, { useState } from "react"; export default function App() { const [num, setNum] = useState(0); function incrementNumber() { setNum(num + 1); } return ( <div> <h1>{num}</h1> <button onClick={incrementNumber}>Increment</button> </div> ); }
Il primo esempio è un componente basato sulla classe mentre il secondo è un componente funzionale. Sebbene questo sia un esempio semplice, notate come il primo esempio sia fasullo rispetto al secondo.
La Convenzione e le regole di Hooks
Prima di approfondire i vari hook, potrebbe essere utile dare un'occhiata alla convenzione e alle regole ad essi applicabili. Ecco alcune delle regole che si applicano agli hook.
- La convenzione di denominazione degli hook dovrebbe iniziare con il prefisso
use
. Quindi, possiamo avereuseState
,useEffect
, ecc. Se stai utilizzando editor di codice moderni come Atom e VSCode, il plug-in ESLint potrebbe essere una funzionalità molto utile per gli hook React. Il plug-in fornisce utili avvisi e suggerimenti sulle migliori pratiche. - Gli hook devono essere chiamati al livello più alto di un componente, prima dell'istruzione return. Non possono essere chiamati all'interno di un'istruzione condizionale, di un ciclo o di funzioni nidificate.
- Gli hook devono essere chiamati da una funzione React (all'interno di un componente React o di un altro hook). Non dovrebbe essere chiamato da una funzione Vanilla JS.
Il gancio useState
L'hook useState
è l'hook React più semplice e utile. Come altri hook integrati, questo hook deve essere importato da react
per essere utilizzato nella nostra applicazione.
import {useState} from 'react'
Per inizializzare lo stato, dobbiamo dichiarare sia lo stato che la sua funzione di aggiornamento e passare un valore iniziale.
const [state, updaterFn] = useState('')
Siamo liberi di chiamare il nostro stato e la funzione di aggiornamento come vogliamo, ma per convenzione, il primo elemento dell'array sarà il nostro stato mentre il secondo elemento sarà la funzione di aggiornamento. È pratica comune anteporre alla nostra funzione di aggiornamento il prefisso impostato seguito dal nome del nostro stato in forma di cammello.
Ad esempio, impostiamo uno stato per contenere i valori di conteggio.
const [count, setCount] = useState(0)
Si noti che il valore iniziale del nostro stato di count
è impostato su 0
e non su una stringa vuota. In altre parole, possiamo inizializzare il nostro stato su qualsiasi tipo di variabile JavaScript, ovvero numero, stringa, booleano, array, oggetto e persino BigInt. Esiste una chiara differenza tra l'impostazione degli stati con l'hook useState
e gli stati dei componenti basati sulla classe. È interessante notare che l'hook useState
restituisce un array, noto anche come variabili di stato e nell'esempio sopra, abbiamo destrutturato l'array in state
e la funzione di updater
.
Componenti di rendering
L'impostazione degli stati con l'hook useState
provoca il rendering del componente corrispondente. Tuttavia, ciò accade solo se React rileva una differenza tra lo stato precedente o precedente e il nuovo stato. React esegue il confronto di stato utilizzando l'algoritmo Javascript Object.is
.
Impostazione degli stati con useState
Il nostro stato di count
può essere impostato su nuovi valori di stato semplicemente passando il nuovo valore alla funzione di aggiornamento setCount
come segue setCount(newValue)
.
Questo metodo funziona quando non vogliamo fare riferimento al valore dello stato precedente. Se desideriamo farlo, dobbiamo passare una funzione alla funzione setCount
.
Supponendo di voler aggiungere 5 alla nostra variabile di count
ogni volta che si fa clic su un pulsante, potremmo fare quanto segue.
import {useState} from 'react' const CountExample = () => { // initialize our count state const [count, setCount] = useState(0) // add 5 to to the count previous state const handleClick = () =>{ setCount(prevCount => prevCount + 5) } return( <div> <h1>{count} </h1> <button onClick={handleClick}>Add Five</button> </div> ) } export default CountExample
Nel codice sopra, abbiamo prima importato l'hook useState
da react
e quindi abbiamo inizializzato lo stato di count
con un valore predefinito di 0. Abbiamo creato un gestore onClick
per incrementare il valore di count
di 5 ogni volta che si fa clic sul pulsante. Quindi abbiamo visualizzato il risultato in un tag h1
.
Impostazione di matrici e stati degli oggetti
Gli stati per gli array e gli oggetti possono essere impostati più o meno allo stesso modo di altri tipi di dati. Tuttavia, se desideriamo mantenere i valori già esistenti, dobbiamo utilizzare l'operatore di diffusione ES6 durante l'impostazione degli stati.
L'operatore spread in Javascript viene utilizzato per creare un nuovo oggetto da un oggetto già esistente. Questo è utile qui perché React
confronta gli stati con l'operazione Object.is
e quindi esegue nuovamente il rendering di conseguenza.
Consideriamo il codice seguente per l'impostazione degli stati al clic del pulsante.
import {useState} from 'react' const StateExample = () => { //initialize our array and object states const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num: 1, name: 'Desmond'}) // set arr to the new array values const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // set obj to the new object values const handleObjClick = () =>{ const newObj = {name: 'Ifeanyi', age: 25} setObj({...obj, ...newObj}) } return( <div> <button onClick ={handleArrClick}>Set Array State</button> <button onClick ={handleObjClick}>Set Object State</button> </div> ) } export default StateExample
Nel codice sopra, abbiamo creato due stati arr
e obj
e li abbiamo inizializzati rispettivamente su alcuni valori di array e oggetto. Abbiamo quindi creato gestori onClick
chiamati handleArrClick
e handleObjClick
per impostare rispettivamente gli stati dell'array e dell'oggetto. Quando handleArrClick
viene attivato, chiamiamo setArr
e utilizziamo l'operatore di diffusione ES6 per diffondere i valori di array già esistenti e aggiungere newArr
ad esso.
Abbiamo fatto la stessa cosa per il gestore handleObjClick
. Qui abbiamo chiamato setObj
, diffuso i valori degli oggetti esistenti utilizzando l'operatore di diffusione ES6 e aggiornato i valori di name
ed age
.
Async Nature Of useState
Come abbiamo già visto, impostiamo gli stati con useState
passando un nuovo valore alla funzione di aggiornamento. Se il programma di aggiornamento viene chiamato più volte, i nuovi valori verranno aggiunti a una coda e il nuovo rendering viene eseguito di conseguenza utilizzando il confronto JavaScript Object.is
.
Gli stati vengono aggiornati in modo asincrono. Ciò significa che il nuovo stato viene prima aggiunto a uno stato in sospeso e, successivamente, lo stato viene aggiornato. Pertanto, potresti comunque ottenere il vecchio valore di stato se accedi allo stato immediatamente in cui è impostato.
Consideriamo il seguente esempio per osservare questo comportamento.
Nel codice sopra, abbiamo creato uno stato di count
usando l'hook useState
. Abbiamo quindi creato un gestore onClick
per incrementare lo stato del count
ogni volta che si fa clic sul pulsante. Osservare che sebbene lo stato di count
sia aumentato, come visualizzato nel tag h2
, lo stato precedente è ancora registrato nella console. Ciò è dovuto alla natura asincrona del gancio.
Se desideriamo ottenere il nuovo stato, possiamo gestirlo in un modo simile a cui gestiremmo le funzioni asincrone. Ecco un modo per farlo.
Qui, abbiamo memorizzato creato newCountValue
per memorizzare il valore di conteggio aggiornato e quindi impostare lo stato di count
con il valore aggiornato. Quindi, abbiamo registrato il valore di conteggio aggiornato nella console.
Il gancio useEffect
useEffect
è un altro importante hook React utilizzato nella maggior parte dei progetti. Fa una cosa simile ai metodi del ciclo di vita componentDidMount
, componentWillUnmount
e componentDidUpdate
del componente basato sulla classe . useEffect
ci offre l'opportunità di scrivere codici imperativi che potrebbero avere effetti collaterali sull'applicazione. Esempi di tali effetti includono la registrazione, gli abbonamenti, le mutazioni, ecc.
L'utente può decidere quando verrà eseguito useEffect
, tuttavia, se non è impostato, gli effetti collaterali verranno eseguiti su ogni rendering o rerendering.
Considera l'esempio seguente.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }
Nel codice sopra, abbiamo semplicemente registrato il count
in useEffect
. Questo verrà eseguito dopo ogni rendering del componente.
A volte, potremmo voler eseguire l'hook una volta (sulla montatura) nel nostro componente. Possiamo ottenere ciò fornendo un secondo parametro per l'hook useEffect
.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }
L'hook useEffect
ha due parametri, il primo parametro è la funzione che vogliamo eseguire mentre il secondo parametro è un array di dipendenze. Se il secondo parametro non viene fornito, l'hook funzionerà continuamente.
Passando una parentesi quadra vuota al secondo parametro dell'hook, indichiamo a React di eseguire l'hook useEffect
solo una volta, sulla montatura. Questo visualizzerà il valore 1
nel tag h1
perché il conteggio verrà aggiornato una volta, da 0 a 1, quando il componente viene montato.
Potremmo anche far funzionare il nostro effetto collaterale ogni volta che cambiano alcuni valori dipendenti. Questo può essere fatto passando questi valori nell'elenco delle dipendenze.
Ad esempio, potremmo fare in modo che useEffect
eseguito ogni volta che count
le modifiche come segue.
import { useState, useEffect } from "react"; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default App;
useEffect
sopra verrà eseguito quando viene soddisfatta una di queste due condizioni.
- In fase di montaggio: dopo il rendering del componente.
- Quando il valore del
count
cambia.
Al momento del montaggio, l'espressione console.log
verrà eseguita e registrerà il count
a 0. Una volta aggiornato il count
, la seconda condizione è soddisfatta, quindi useEffect
viene eseguito di nuovo, questo continuerà ogni volta che si fa clic sul pulsante.
Una volta fornito il secondo argomento per useEffect
, ci si aspetta che gli passiamo tutte le dipendenze. Se hai installato ESLINT
, mostrerà un errore lint se una dipendenza non viene passata all'elenco dei parametri. Ciò potrebbe anche far comportare l'effetto collaterale in modo imprevisto, soprattutto se dipende dai parametri che non vengono passati.
Ripulire l'effetto
useEffect
ci consente anche di ripulire le risorse prima che il componente venga smontato. Ciò potrebbe essere necessario per evitare perdite di memoria e rendere l'applicazione più efficiente. Per fare ciò, restituiremmo la funzione di pulizia alla fine dell'hook.
useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })
L'hook useEffect
sopra registrerà il mounted
quando il componente è montato. Smontaggio... la pulizia qui verrà registrata quando il componente viene smontato. Ciò può verificarsi quando il componente viene rimosso dall'interfaccia utente.
Il processo di pulizia in genere segue il modulo seguente.
useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })
Anche se potresti non trovare così tanti casi d'uso per gli abbonamenti useEffect
, è utile quando hai a che fare con abbonamenti e timer. In particolare, quando si ha a che fare con i socket Web, potrebbe essere necessario annullare l'iscrizione alla rete per risparmiare risorse e migliorare le prestazioni quando il componente viene smontato.
Recupero e recupero dei dati con useEffect
Uno dei casi d'uso più comuni useEffect
è il recupero e il precaricamento dei dati da un'API.
Per illustrare ciò, utilizzeremo i dati utente falsi che ho creato da JSONPlaceholder
per recuperare i dati con l'hook useEffect
.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users"; useEffect(() => { const fetchUsers = async () => { const { data } = await axios.get(endPoint); setUsers(data); }; fetchUsers(); }, []); return ( <div className="App"> {users.map((user) => ( <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> ))} </div> ); }
Nel codice sopra, abbiamo creato uno stato users
usando l'hook useState
. Quindi abbiamo recuperato i dati da un'API utilizzando Axios. Questo è un processo asincrono, quindi abbiamo usato la funzione async/await, avremmo anche potuto usare il punto e poi la sintassi. Poiché abbiamo recuperato un elenco di utenti, lo abbiamo semplicemente mappato per visualizzare i dati.
Si noti che abbiamo passato un parametro vuoto all'hook. Ciò garantisce che venga chiamato solo una volta quando il componente viene montato.
Possiamo anche recuperare i dati quando cambiano alcune condizioni. Lo mostreremo nel codice qui sotto.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users"; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = await axios.get(`${endPoint}/${currentID}`}); setUser(data); }; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId)); }; const moveToPrevUser = () => { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); }; return ( <div className="App"> <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> <button onClick={moveToPrevUser}>Prev</button> <button onClick={moveToNextUser}>Next</button> </div> ); }
Qui abbiamo creato due hook useEffect
. Nel primo, abbiamo usato la sintassi punto poi per ottenere tutti gli utenti dalla nostra API. Ciò è necessario per determinare il numero di utenti.
Abbiamo quindi creato un altro hook useEffect
per ottenere un utente basato id
. Questo useEffect
i dati ogni volta che l'id cambia. Per garantire ciò, abbiamo passato l' id
nell'elenco delle dipendenze.
Successivamente, abbiamo creato funzioni per aggiornare il valore del nostro id
ogni volta che si fa clic sui pulsanti. Una volta che il valore id
cambia, useEffect
verrà eseguito di nuovo e recupererà i dati.
Se vogliamo, possiamo anche ripulire o annullare il token basato sulla promessa in Axios, potremmo farlo con il metodo di pulizia discusso sopra.
useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = await axios.get(`${endPoint}/${num}`, { cancelToken: source.token }); setUser(data); }; fetchUsers(); return () => source.cancel(); }, [num]);
Qui, abbiamo passato il token di Axios come secondo parametro ad axios.get
. Quando il componente si smonta, abbiamo quindi annullato l'abbonamento chiamando il metodo cancel dell'oggetto di origine.
Il gancio useReducer
L'hook useReducer
è un hook React molto utile che fa una cosa simile useState
. Secondo la documentazione di React, questo hook dovrebbe essere utilizzato per gestire una logica più complessa rispetto useState
. È degno di nota che l'hook useState
è implementato internamente con l'hook useReducer.
L'hook accetta un riduttore come argomento e può opzionalmente prendere lo stato iniziale e una funzione init come argomenti.
const [state, dispatch] = useReducer(reducer, initialState, init)
Qui, init
è una funzione e viene utilizzata ogni volta che vogliamo creare pigramente lo stato iniziale.
Diamo un'occhiata a come implementare l'hook useReducer
creando una semplice app da fare come mostrato nella sandbox sottostante.
Prima di tutto, dovremmo creare il nostro riduttore per tenere gli stati.
export const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; const reducer = (state, action) => { switch (action.type) { case ADD_TODO: const newTodo = { id: action.id, text: action.text, completed: false }; return [...state, newTodo]; case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id); case COMPLETE_TODO: const completeTodo = state.map((todo) => { if (todo.id === action.id) { return { ...todo, completed: !todo.completed }; } else { return todo; } }); return completeTodo; default: return state; } }; export default reducer;
Abbiamo creato tre costanti corrispondenti ai nostri tipi di azione. Avremmo potuto usare le stringhe direttamente, ma questo metodo è preferibile per evitare errori di battitura.
Quindi abbiamo creato la nostra funzione di riduzione. Come in Redux
, il riduttore deve prendere lo stato e l'oggetto dell'azione. Ma a differenza di Redux, non è necessario inizializzare il nostro riduttore qui.
Inoltre, per molti casi d'uso di gestione dello stato, un useReducer
insieme dispatch
esposto tramite il contesto può consentire a un'applicazione più ampia di attivare azioni, aggiornare state
e ascoltarlo.
Quindi abbiamo utilizzato le istruzioni switch
per verificare il tipo di azione passato dall'utente. Se il tipo di azione è ADD_TODO
, vogliamo passare una nuova cosa da fare e se è REMOVE_TODO
, vogliamo filtrare le cose da fare e rimuovere quella che corrisponde id
passato dall'utente. Se è COMPLETE_TODO
, vogliamo mappare le cose da fare e alternare quella con l' id
passato dall'utente.
Ecco il file App.js
in cui abbiamo implementato il reducer
.
import { useReducer, useState } from "react"; import "./styles.css"; import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //We could also pass an empty array as the initial state //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); dispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="App"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit">+</button> </form> <div className="todos"> {state.map((todo) => ( <div key={todo.id} className="todoItem"> <p className={todo.completed && "strikethrough"}>{todo.text}</p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ))} </div> </div> ); }
Qui, abbiamo creato un modulo contenente un elemento di input, per raccogliere l'input dell'utente, e un pulsante per attivare l'azione. Quando il modulo viene inviato, abbiamo inviato un'azione di tipo ADD_TODO
, passando un nuovo id e un testo da fare. Abbiamo creato un nuovo id incrementando il valore id precedente di 1. Abbiamo quindi deselezionato la casella di testo di input. Per eliminare e completare le cose da fare, abbiamo semplicemente inviato le azioni appropriate. Questi sono già stati implementati nel riduttore come mostrato sopra.
Tuttavia, la magia accade perché stiamo usando il gancio useReducer
. Questo hook accetta il riduttore e lo stato iniziale e restituisce lo stato e la funzione di invio. Qui, la funzione dispatch ha lo stesso scopo della funzione setter per l'hook useState
e possiamo chiamarla come vogliamo invece di dispatch
.
Per visualizzare le cose da fare, abbiamo semplicemente mappato l'elenco delle cose da fare restituite nel nostro oggetto stato come mostrato nel codice sopra.
Questo mostra la potenza del gancio useReducer
. Potremmo anche ottenere questa funzionalità con l'hook useState
ma, come puoi vedere dall'esempio sopra, l'hook useReducer
ci ha aiutato a mantenere le cose più ordinate. useReducer
è spesso utile quando l'oggetto stato è una struttura complessa e viene aggiornato in modi diversi rispetto a una semplice sostituzione di valore. Inoltre, una volta che queste funzioni di aggiornamento diventano più complicate, useReducer
rende facile mantenere tutta quella complessità in una funzione riduttore (che è una pura funzione JS) rendendo molto facile scrivere test per la sola funzione riduttore.
Avremmo anche potuto passare il terzo argomento useReducer
per creare pigramente lo stato iniziale. Ciò significa che potremmo calcolare lo stato iniziale in una funzione init
.
Ad esempio, potremmo creare una funzione init
come segue:
const initFunc = () => [ { id: id, text: "First Item", completed: false } ]
e poi passalo al nostro hook useReducer
.
const [state, dispatch] = useReducer(reducer, initialState, initFunc)
Se lo facciamo, initFunc
sovrascriverà lo stato initialState
che abbiamo fornito e lo stato iniziale verrà calcolato pigramente.
L'hook useContext
L'API React Context fornisce un modo per condividere stati o dati nell'albero dei componenti di React. L'API è disponibile in React, come funzionalità sperimentale, da un po', ma è diventata sicura da usare in React 16.3.0. L'API semplifica la condivisione dei dati tra i componenti eliminando la perforazione dell'elica.
Sebbene tu possa applicare React Context all'intera applicazione, è anche possibile applicarlo a una parte dell'applicazione.
Per utilizzare l'hook, devi prima creare un contesto usando React.createContext
e questo contesto può quindi essere passato all'hook.
Per dimostrare l'uso useContext
, creiamo una semplice app che aumenterà la dimensione del carattere in tutta la nostra applicazione.
Creiamo il nostro contesto nel file context.js
.
import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;
Qui abbiamo creato un contesto e gli abbiamo passato un valore iniziale di 16
, quindi abbiamo esportato il contesto. Quindi, colleghiamo il nostro contesto alla nostra applicazione.
import FontSizeContext from "./context"; import { useState } from "react"; import PageOne from "./PageOne"; import PageTwo from "./PageTwo"; const App = () => { const [size, setSize] = useState(16); return ( <FontSizeContext.Provider value={size}> <PageOne /> <PageTwo /> <button onClick={() => setSize(size + 5)}>Increase font</button> <button onClick={() => setSize((prevSize) => Math.min(11, prevSize - 5)) } > Decrease font </button> </FontSizeContext.Provider> ); }; export default App;
Nel codice sopra, abbiamo racchiuso l'intero albero dei componenti con FontSizeContext.Provider
e passato la size
al suo valore prop. Qui, size
è uno stato creato con l'hook useState
. Questo ci permette di cambiare il valore prop ogni volta che cambia lo stato della size
. Avvolgendo l'intero componente con il Provider
, possiamo accedere al contesto ovunque nella nostra applicazione.
Ad esempio, abbiamo avuto accesso al contesto in <PageOne />
e <PageTwo />
. Di conseguenza, la dimensione del carattere aumenterà su questi due componenti quando la aumentiamo dal file App.js
Possiamo aumentare o diminuire la dimensione del carattere dai pulsanti come mostrato sopra e una volta fatto, la dimensione del carattere cambia in tutta l'applicazione.
import { useContext } from "react"; import context from "./context"; const PageOne = () => { const size = useContext(context); return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>; }; export default PageOne;
Qui, abbiamo effettuato l'accesso al contesto utilizzando l'hook useContext
dal nostro componente PageOne
. Abbiamo quindi utilizzato questo contesto per impostare la nostra proprietà font-size. Una procedura simile si applica al file PageTwo.js
.
Temi o altre configurazioni a livello di app di ordine superiore sono buoni candidati per i contesti.
Utilizzo useContext
e useReducer
Se utilizzato con l'hook useReducer
, useContext
ci consente di creare il nostro sistema di gestione dello stato. Possiamo creare stati globali e gestirli facilmente nella nostra applicazione.
Miglioriamo la nostra applicazione di cose da fare utilizzando l'API di contesto.
Come al solito, dobbiamo creare un todoContext
nel file todoContext.js
.
import { createContext } from "react"; const initialState = []; export default createContext(initialState);
Qui abbiamo creato il contesto, passando un valore iniziale di un array vuoto. Quindi abbiamo esportato il contesto.
Eseguiamo il refactoring del nostro file App.js
separando l'elenco delle cose da fare e gli elementi.
import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <div className="app"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </TodoContext.Provider> ); }
Qui, abbiamo eseguito il wrapping del nostro file App.js
con TodoContext.Provider
, quindi gli abbiamo passato i valori di ritorno del nostro todoReducer
. Ciò rende lo stato del riduttore e la funzione di dispatch
accessibili in tutta la nostra applicazione.
Abbiamo quindi separato la visualizzazione delle cose da fare in un componente TodoList
. L'abbiamo fatto senza perforazione dell'elica, grazie all'API Context. Diamo un'occhiata al file TodoList.js
.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ( <div className="todos"> {state.map((todo) => ( <Todo key={todo.id} todo={todo} /> ))} </div> ); }; export default TodoList;
Usando la destrutturazione dell'array, possiamo accedere allo stato (lasciando la funzione di invio) dal contesto usando l'hook useContext
. Possiamo quindi mappare lo stato e visualizzare le cose da fare. Lo abbiamo ancora estratto in un componente Todo
. La funzione della mappa ES6+ ci richiede di passare una chiave univoca e poiché abbiamo bisogno di una cosa da fare specifica, la passiamo anche insieme.
Diamo un'occhiata al componente Todo
.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="todoItem"> <p className={todo.completed ? "strikethrough" : "nostrikes"}> {todo.text} </p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ); }; export default Todo;
Usando sempre la destrutturazione dell'array, abbiamo effettuato l'accesso alla funzione di invio dal contesto. Questo ci permette di definire la funzione completeTodo
e removeTodo
come già discusso nella sezione useReducer
. Con il prop todo
passato da todoList.js
possiamo visualizzare un elemento da fare. Possiamo anche contrassegnarlo come completato e rimuovere la cosa da fare come riteniamo opportuno.
È anche possibile annidare più di un provider di contesto nella radice della nostra applicazione. Ciò significa che possiamo utilizzare più di un contesto per eseguire funzioni diverse in un'applicazione.
Per dimostrarlo, aggiungiamo un tema all'esempio delle cose da fare.
Ecco cosa costruiremo.
Ancora una volta, dobbiamo creare themeContext
. Per fare ciò, crea un file themeContext.js
e aggiungi i seguenti codici.
import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);
Qui, abbiamo creato un contesto e passato colors.light
come valore iniziale. Definiamo i colori con questa proprietà nel file colors.js
.
const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;
Nel codice sopra, abbiamo creato un oggetto colors
contenente proprietà chiare e scure. Ogni proprietà ha backgroundColor
e oggetto color
.
Successivamente, creiamo il themeReducer
per gestire gli stati del tema.
import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;
Come tutti i riduttori, il themeReducer
prende lo stato e l'azione. Quindi utilizza l'istruzione switch
per determinare l'azione corrente. Se è di tipo LIGHT
, assegniamo semplicemente gli oggetti di scena Colors.light
e se è di tipo DARK
, visualizziamo gli oggetti di scena Colors.dark
. Avremmo potuto farlo facilmente con l'hook useState
, ma scegliamo useReducer
per portare il punto a casa.
Dopo aver impostato il themeReducer
, possiamo quindi integrarlo nel nostro file App.js
import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <ThemeContext.Provider value={[ themeState, themeSetter ]} > <div className="app" style={{ ...themeState }}> <ThemeToggler /> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </ThemeContext.Provider> </TodoContext.Provider> ); }
Nel codice sopra, abbiamo aggiunto alcune cose alla nostra applicazione già esistente. Abbiamo iniziato importando ThemeContext
, themeReducer
, ThemeToggler
e Colors
. We created a reducer using the useReducer
hook, passing the themeReducer
and an initial value of Colors.light
to it. This returned the themeState
and themeDispatch
to us.
We then nested our component with the provider function from the ThemeContext
, passing the themeState
and the dispatch
functions to it. We also added theme styles to it by spreading out the themeStates
. This works because the colors
object already defined properties similar to what the JSX styles will accept.
However, the actual theme toggling happens in the ThemeToggler
component. Diamo un'occhiata.
import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return ( <div> <button onClick={toggleTheme}> {showLight ? "Change to Dark Theme" : "Change to Light Theme"} </button> </div> ); }; export default ThemeToggler;
In this component, we used the useContext
hook to retrieve the values we passed to the ThemeContext.Provider
from our App.js
file. As shown above, these values include the ThemeState
, dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight
to determine the current theme. This allows us to easily change the button text depending on the current theme.
The useMemo
Hook
The useMemo
hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo
will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.
The hook can be used as follows:
const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])
Let's consider three cases of the useMemo
hook.
- When the dependency values, a and b remain the same.
TheuseMemo
hook will return the already computed memoized value without recomputation. - When the dependency values, a and b change.
The hook will recompute the value. - When no dependency value is passed.
The hook will recompute the value.
Let's take a look at an example to demonstrate this concept.
In the example below, we'll be computing the PAYE and Income after PAYE of a company's employees with fake data from JSONPlaceholder.
The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.
This is shown in the sandbox below.
First, we queried the API to get the employees' data. We also get data for each employee (with respect to their employee id).
const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);
Abbiamo usato axios
e il metodo async/await
nel primo useEffect
e poi il punto e poi la sintassi nel secondo. Questi due approcci funzionano allo stesso modo.
Quindi, utilizzando i dati dei dipendenti che abbiamo ottenuto da sopra, calcoliamo le variabili di rilievo:
const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);
Questo è un calcolo abbastanza complesso e quindi abbiamo dovuto racchiuderlo in un hook useMemo
per memorizzarlo o ottimizzarlo. Memorizzarlo in questo modo assicurerà che il calcolo non venga ricalcolato se si tenta di accedere nuovamente allo stesso dipendente.
Inoltre, utilizzando i valori di sgravio fiscale ottenuti sopra, vorremmo calcolare il PAYE e il reddito dopo PAYE.
const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);
Abbiamo eseguito il calcolo delle tasse (un calcolo abbastanza complesso) utilizzando le variabili fiscali sopra calcolate e poi l'abbiamo memorizzato con l'hook useMemo
.
Il codice completo è disponibile qui.
Ciò segue la procedura di calcolo dell'imposta qui riportata. Per prima cosa abbiamo calcolato lo sgravio fiscale considerando il reddito, il numero dei figli e il numero dei parenti a carico. Quindi, abbiamo moltiplicato gradualmente il reddito imponibile per le aliquote PIT. Sebbene il calcolo in questione non sia del tutto necessario per questo tutorial, viene fornito per mostrarci perché potrebbe essere necessario useMemo
. Anche questo è un calcolo abbastanza complesso e quindi potrebbe essere necessario memorizzarlo con useMemo
come mostrato sopra.
Dopo aver calcolato i valori, abbiamo semplicemente visualizzato il risultato.
Nota quanto segue useMemo
.
-
useMemo
dovrebbe essere utilizzato solo quando è necessario ottimizzare il calcolo. In altre parole, quando il ricalcolo è costoso. - Si consiglia di scrivere prima il calcolo senza memorizzazione e di memorizzarlo solo se causa problemi di prestazioni.
- L'uso non necessario e irrilevante del gancio
useMemo
può anche aggravare i problemi di prestazioni. - A volte, troppa memorizzazione può anche causare problemi di prestazioni.
Il gancio useCallback
useCallback
lo stesso scopo di useMemo
ma restituisce una richiamata memorizzata invece di un valore memorizzato. In altre parole, useCallback
a passare useMemo
senza una chiamata di funzione.
Ad esempio, considera i seguenti codici di seguito.
import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return( <div> ... </div> ) } export default MemoizationExample
Nell'esempio precedente, sia memoResult
che callbackResult
daranno lo stesso valore di 12
. Qui useCallback
restituirà un valore memorizzato. Tuttavia, potremmo anche fare in modo che restituisca un callback memorizzato passandolo come una funzione.
useCallback
seguito restituirà una richiamata memorizzata.
... const callbackResult = useCallback(() => a + b, [a, b]) ...
Possiamo quindi attivare il callback quando viene eseguita un'azione o in un hook useEffect
.
import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return ( <div> <button onClick= {() => console.log(callbackResult())}> Trigger Callback </button> </div> ) } export default memoizationExample
Nel codice precedente, abbiamo definito una funzione di callback utilizzando l'hook useCallback
. Abbiamo quindi chiamato la callback in un hook useEffect
quando il componente viene montato e anche quando si fa clic su un pulsante.
Sia useEffect
che il clic del pulsante producono lo stesso risultato.
Si noti che i concetti, le cose da fare e da non fare che si applicano useMemo
si applicano anche useCallback
. Possiamo ricreare l'esempio useMemo
con useCallback
.
Il gancio useRef
useRef
restituisce un oggetto che può persistere in un'applicazione. L'hook ha solo una proprietà, current
, e possiamo facilmente passargli un argomento.
Serve allo stesso scopo di un createRef
utilizzato nei componenti basati su classi. Possiamo creare un riferimento con questo hook come segue:
const newRef = useRef('')
Qui abbiamo creato un nuovo ref chiamato newRef
e gli abbiamo passato una stringa vuota.
Questo gancio viene utilizzato principalmente per due scopi:
- Accesso o manipolazione del DOM e
- Memorizzazione di stati mutabili: è utile quando non si desidera che il componente venga riprodotto quando un valore cambia.
Manipolazione del DOM
Quando passato a un elemento DOM, l'oggetto ref punta a quell'elemento e può essere utilizzato per accedere ai suoi attributi e proprietà DOM.
Ecco un esempio molto semplice per dimostrare questo concetto.
import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return( <div> <h1 className='topheading' ref={headingRef}>This is a h1 element</h1> </div> ) } export default RefExample
Nell'esempio sopra, abbiamo definito headingRef
usando l'hook useRef
passando una stringa vuota. Quindi impostiamo il ref nel tag h1
passando ref = {headingRef}
. Impostando questo ref, abbiamo chiesto headingRef
di puntare al nostro elemento h1
. Ciò significa che possiamo accedere alle proprietà del nostro elemento h1
dal rif.
Per vedere questo, se controlliamo il valore di console.log(headingRef)
, otterremo {current: HTMLHeadingElement}
o {current: h1}
e possiamo valutare tutte le proprietà o gli attributi dell'elemento. Una cosa simile si applica a qualsiasi altro elemento HTML.
Ad esempio, potremmo rendere il testo in corsivo quando il componente viene montato.
useEffect(() => { headingRef.current.style.font; }, []);
Possiamo anche cambiare il testo in qualcos'altro.
... headingRef.current.innerHTML = "A Changed H1 Element"; ...
Possiamo anche cambiare il colore di sfondo del contenitore genitore.
... headingRef.current.parentNode.style.backgroundColor = "red"; ...
Qualsiasi tipo di manipolazione DOM può essere eseguita qui. Osserva che headingRef.current
può essere letto allo stesso modo di document.querySelector('.topheading')
.
Un caso d'uso interessante useRef
nella manipolazione dell'elemento DOM è focalizzare il cursore sull'elemento di input. Esaminiamolo velocemente.
import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return( <div> <input ref={inputRef} /> <button onClick = {() => inputRef.current.focus()}>Focus on Input </button> </div> ) } export default inputRefExample
Nel codice precedente, abbiamo creato inputRef
utilizzando l'hook useRef
e quindi gli abbiamo chiesto di puntare all'elemento di input. Abbiamo quindi messo a fuoco il cursore sul riferimento di input quando il componente viene caricato e quando si fa clic sul pulsante utilizzando inputRef.current.focus()
. Questo è possibile perché focus()
è un attributo di elementi di input e quindi il ref sarà in grado di valutare i metodi.
I riferimenti creati in un componente padre possono essere valutati sul componente figlio inoltrandolo utilizzando React.forwardRef()
. Diamo un'occhiata.
Creiamo prima un altro componente NewInput.js
e aggiungiamo i seguenti codici.
import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;
Questo componente accetta props
di scena e ref
. Abbiamo passato il ref alla sua ref prop e props.val
alla sua prop placeholder. I componenti Regular React non accettano un attributo ref
. Questo attributo è disponibile solo quando lo avvolgiamo con React.forwardRef
come mostrato sopra.
Possiamo quindi facilmente chiamarlo nel componente padre.
... <NewInput val="Just an example" ref={inputRef} /> ...
Memorizzazione degli stati mutevoli
I riferimenti non vengono utilizzati solo per manipolare elementi DOM, ma possono anche essere utilizzati per memorizzare valori modificabili senza eseguire nuovamente il rendering dell'intero componente.
L'esempio seguente rileverà il numero di volte in cui viene fatto clic su un pulsante senza eseguire nuovamente il rendering del componente.
import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return ( <div className="App"> <button onClick={increment}>Increment </button> </div> ); }
Nel codice sopra, abbiamo incrementato countRef
quando si fa clic sul pulsante e quindi lo abbiamo registrato sulla console. Sebbene il valore venga incrementato come mostrato nella console, non saremo in grado di vedere alcun cambiamento se proviamo a valutarlo direttamente nel nostro componente. Si aggiornerà nel componente solo quando esegue nuovamente il rendering.
Si noti che mentre useState
è asincrono, useRef
è sincrono. In altre parole, il valore è disponibile subito dopo l'aggiornamento.
Il gancio useLayoutEffect
Come l'hook useEffect
, useLayoutEffect
viene chiamato dopo che il componente è stato montato e renderizzato. Questo hook si attiva dopo la mutazione DOM e lo fa in modo sincrono. Oltre a essere chiamato in modo sincrono dopo la mutazione DOM, useLayoutEffect
fa la stessa cosa di useEffect
.
useLayoutEffect
deve essere utilizzato solo per eseguire la mutazione DOM o la misurazione correlata al DOM, altrimenti è necessario utilizzare l'hook useEffect
. L'uso useEffect
per le funzioni di mutazione DOM può causare alcuni problemi di prestazioni come lo sfarfallio, ma useLayoutEffect
li gestisce perfettamente mentre viene eseguito dopo che si sono verificate le mutazioni.
Diamo un'occhiata ad alcuni esempi per dimostrare questo concetto.
- Otterremo la larghezza e l'altezza della finestra durante il ridimensionamento.
import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return ( <div> <p>width: {windowSize.width}</p> <p>height: {windowSize.height}</p> </div> ) } export default ResizeExample
Nel codice sopra, abbiamo creato uno stato windowSize
con le proprietà di larghezza e altezza. Quindi impostiamo lo stato rispettivamente alla larghezza e all'altezza della finestra corrente quando la finestra viene ridimensionata. Abbiamo anche ripulito il codice quando si smonta. Il processo di pulizia è essenziale in useLayoutEffect
per ripulire la manipolazione DOM e migliorare l'efficienza.
- Sfociamo un testo con
useLayoutEffect
.
import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return ( <div className="App"> <p ref={paragraphRef}>This is the text to blur</p> </div> ); }
Abbiamo usato useRef
e useLayoutEffect
insieme nel codice sopra. Per prima cosa abbiamo creato un riferimento, paragraphRef
per puntare al nostro paragrafo. Quindi abbiamo creato un listener di eventi al clic per monitorare quando si fa clic sul paragrafo e quindi sfocarlo utilizzando le proprietà di stile che abbiamo definito. Infine, abbiamo ripulito il listener di eventi utilizzando removeEventListener
.
Gli useDispatch
e useSelector
useDispatch
è un hook Redux per inviare (attivare) azioni in un'applicazione. Prende un oggetto azione come argomento e richiama l'azione. useDispatch
è l'equivalenza dell'hook con mapDispatchToProps
.
D'altra parte, useSelector
è un hook Redux per valutare gli stati Redux. Richiede una funzione per selezionare l'esatto riduttore Redux dallo store e quindi restituisce gli stati corrispondenti.
Una volta che il nostro negozio Redux è connesso a un'applicazione React tramite il provider Redux, possiamo invocare le azioni con useDispatch
e accedere agli stati con useSelector
. Ogni azione e stato Redux può essere valutato con questi due hook.
Nota che questi stati vengono forniti con React Redux (un pacchetto che semplifica la valutazione del negozio Redux in un'applicazione React). Non sono disponibili nella libreria principale di Redux.
Questi ganci sono molto semplici da usare. Innanzitutto, dobbiamo dichiarare la funzione di invio e quindi attivarla.
import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExample
Nel codice sopra, abbiamo importato useDispatch
e useSelector
da react-redux
. Quindi, in un hook useEffect
, abbiamo inviato l'azione. Potremmo definire l'azione in un altro file e quindi chiamarla qui oppure potremmo definirla direttamente come mostrato nella chiamata useEffect
.
Una volta che avremo inviato le azioni, i nostri stati saranno disponibili. Possiamo quindi recuperare lo stato usando l'hook useSelector
come mostrato. Gli stati possono essere usati nello stesso modo in cui useremmo gli stati dell'hook useState
.
Diamo un'occhiata a un esempio per dimostrare questi due hook.
Per dimostrare questo concetto, dobbiamo creare un negozio Redux, un riduttore e delle azioni. Per semplificare le cose qui, utilizzeremo la libreria Redux Toolkit con il nostro database falso di JSONPlaceholder.
Abbiamo bisogno di installare i seguenti pacchetti per iniziare. Esegui i seguenti comandi bash.
npm i redux @reduxjs/toolkit react-redux axios
Innanzitutto, creiamo il employeesSlice.js
per gestire il riduttore e l'azione per l'API dei nostri dipendenti.
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: [], loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;
Questa è la configurazione standard per il toolkit Redux. Abbiamo usato createAsyncThunk
per accedere al middleware Thunk
per eseguire azioni asincrone. Questo ci ha permesso di recuperare l'elenco dei dipendenti dall'API. Abbiamo quindi creato i employeesSlice
e restituito, "caricamento", "errore" e i dati dei dipendenti a seconda dei tipi di azione.
Redux toolkit semplifica anche la configurazione del negozio. Ecco il negozio.
import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;
Qui, abbiamo utilizzato combineReducers
per raggruppare i riduttori e la funzione configureStore
fornita da Redux toolkit per configurare il negozio.
Procediamo a usarlo nella nostra applicazione.
Innanzitutto, dobbiamo connettere Redux alla nostra applicazione React. Idealmente, questo dovrebbe essere fatto alla radice della nostra applicazione. Mi piace farlo nel file index.js
.
import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <StrictMode> <App /> </StrictMode> </Provider>, rootElement );
Qui, ho importato lo store che ho creato sopra e anche Provider
da react-redux
.
Quindi, ho avvolto l'intera applicazione con la funzione Provider
, passandogli l'archivio. Questo rende il negozio accessibile in tutta la nostra applicazione.
Possiamo quindi procedere a utilizzare gli useDispatch
e useSelector
per recuperare i dati.
Facciamolo nel nostro file App.js
import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ( <div className="App"> {loading ? ( "Loading..." ) : error ? ( <div>{error}</div> ) : ( <> <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <h3>{`${employee.firstName} ${employee.lastName}`}</h3> </div> ))} </> )} </div> ); }
Nel codice precedente, abbiamo utilizzato l'hook useDispatch
per richiamare l'azione fetchEmployees
creata nel file employeesSlice.js
. In questo modo i dipendenti dichiarano di essere disponibili nella nostra applicazione. Quindi, abbiamo usato l'hook useSelector
per ottenere gli stati. Successivamente, abbiamo visualizzato i risultati mappando i employees
.
Il gancio useHistory
La navigazione è molto importante in un'applicazione React. Sebbene tu possa ottenere questo in un paio di modi, React Router fornisce un modo semplice, efficiente e popolare per ottenere un routing dinamico in un'applicazione React. Inoltre, React Router fornisce un paio di hook per valutare lo stato del router ed eseguire la navigazione sul browser ma per utilizzarli è necessario prima configurare correttamente l'applicazione.
Per utilizzare qualsiasi hook React Router, dovremmo prima avvolgere la nostra applicazione con BrowserRouter
. Possiamo quindi annidare i percorsi con Switch
e Route
.
Ma prima, dobbiamo installare il pacchetto eseguendo i seguenti comandi.
npm install react-router-dom
Quindi, dobbiamo configurare la nostra applicazione come segue. Mi piace farlo nel mio file App.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return ( <div className="App"> <Router> <Switch> <Route path='/'> <Employees /> </Route> ... </Switch> </Router> </div> ); }
Potremmo avere più percorsi possibili a seconda del numero di componenti che desideriamo renderizzare. Qui, abbiamo reso solo il componente Employees
. L'attributo path
indica a React Router DOM il percorso del componente e può essere valutato con query string o vari altri metodi.
L'ordine qui conta. Il percorso principale dovrebbe essere posizionato sotto il percorso figlio e così via. Per ignorare questo ordine, devi includere la parola chiave exact
nel percorso principale.
<Route path='/' exact > <Employees /> </Route>
Ora che abbiamo impostato il router, possiamo quindi utilizzare l'hook useHistory
e altri hook React Router nella nostra applicazione.
Per utilizzare l'hook useHistory
, dobbiamo prima dichiararlo come segue.
import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }
Se registriamo la cronologia sulla console, vedremo diverse proprietà ad essa associate. Questi includono block
, createHref
, go
, goBack
, goForward
, length
, listen
, location
, push
, replace
. Sebbene tutte queste proprietà siano utili, molto probabilmente utilizzerai history.push
e history.replace
più spesso di altre proprietà.
Usiamo questa proprietà per spostarci da una pagina all'altra.
Supponendo di voler recuperare i dati su un determinato dipendente quando facciamo clic sui loro nomi. Possiamo utilizzare l'hook useHistory
per passare alla nuova pagina in cui verranno visualizzate le informazioni del dipendente.
function moveToPage = (id) =>{ history.push(`/employees/${id}`) }
Possiamo implementarlo nel nostro file Employee.js
aggiungendo quanto segue.
import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return ( <div> ... <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <span>{`${employee.firstName} ${employee.lastName} `}</span> <button onClick={pushToPage(employee.id)}> » </button> </div> ))} </div> ); }
Nella funzione pushToPage
, abbiamo utilizzato la history
useHistory
per passare alla pagina del dipendente e passare l'ID del dipendente a fianco.
Il gancio useLocation
Questo gancio viene fornito anche con React Router DOM. È un hook molto popolare utilizzato per lavorare con il parametro della stringa di query. Questo hook è simile a window.location
nel browser.
import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample
L'hook useLocation
restituisce il nome del pathname
, il parametro di search
, l' hash
e state
. I parametri più comunemente usati includono il nome del pathname
e search
, ma puoi ugualmente usare hash
e state
molto nella tua applicazione.
La proprietà del pathname
della posizione restituirà il percorso che abbiamo impostato nella configurazione del Route
. Mentre search
restituirà il parametro di ricerca della query, se presente. Ad esempio, se passiamo 'http://mywebsite.com/employee/?id=1'
alla nostra query, il pathname
sarebbe /employee
e la search
sarebbe ?id=1
.
Possiamo quindi recuperare i vari parametri di ricerca utilizzando pacchetti come query-string o codificandoli.
Il gancio useParams
Se impostiamo la nostra Route con un parametro URL nel suo attributo path, possiamo valutare quei parametri come coppie chiave/valore con l'hook useParams
.
Ad esempio, supponiamo di avere il seguente Percorso.
<Route path='/employees/:id' > <Employees /> </Route>
Il percorso si aspetta un id dinamico al posto di :id
.
Con l'hook useParams
, possiamo valutare l'id passato dall'utente, se presente.
Ad esempio, supponendo che l'utente passi quanto segue in funzione con history.push
,
function goToPage = () => { history.push(`/employee/3`) }
Possiamo usare l'hook useParams
per accedere a questo parametro URL come segue.
import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample
Se registriamo params
sulla console, otterremo il seguente oggetto {id: "3"}
.
Hook useRouteMatch
Questo hook fornisce l'accesso all'oggetto match. Restituisce la corrispondenza più vicina a un componente se non gli viene fornito alcun argomento.
L'oggetto match restituisce diversi parametri tra cui il path
(lo stesso del percorso specificato in Route), l' URL
, l'oggetto params
e isExact
.
Ad esempio, possiamo utilizzare useRouteMatch
per restituire componenti in base al percorso.
import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( <Employee /> ) : ( <Admin /> ); }; export default CustomRoute;
Nel codice precedente, abbiamo impostato il percorso di un percorso con useRouteMatch
e quindi abbiamo eseguito il rendering del componente <Employee />
o <Admin />
a seconda del percorso selezionato dall'utente.
Affinché funzioni, dobbiamo ancora aggiungere il percorso al nostro file App.js
... <Route> <CustomRoute /> </Route> ...
Costruire un gancio personalizzato
Secondo la documentazione di React, la creazione di un hook personalizzato ci consente di estrarre una logica in una funzione riutilizzabile. Tuttavia, devi assicurarti che tutte le regole che si applicano agli hook React si applichino al tuo hook personalizzato. Controlla le regole di React hook nella parte superiore di questo tutorial e assicurati che il tuo hook personalizzato sia conforme a ciascuno di essi.
Gli hook personalizzati ci consentono di scrivere funzioni una volta e di riutilizzarle ogni volta che sono necessarie e quindi obbedendo al principio DRY.
Ad esempio, potremmo creare un hook personalizzato per ottenere la posizione di scorrimento sulla nostra pagina come segue.
import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };
Qui, abbiamo definito un hook personalizzato per determinare la posizione di scorrimento su una pagina. Per ottenere ciò, abbiamo prima creato uno stato, scrollPos
, per memorizzare la posizione di scorrimento. Poiché questo modificherà il DOM, dobbiamo usare useLayoutEffect
invece di useEffect
. Abbiamo aggiunto un listener di eventi di scorrimento per acquisire le posizioni di scorrimento xey e quindi ripulito il listener di eventi. Infine, siamo tornati alla posizione di scorrimento.
Possiamo usare questo hook personalizzato ovunque nella nostra applicazione chiamandolo e usandolo proprio come useremmo qualsiasi altro stato.
import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App
Qui, abbiamo importato l'hook personalizzato useScrollPos
che abbiamo creato sopra. Quindi lo abbiamo inizializzato e quindi registrato il valore sulla nostra console. Se scorriamo sulla pagina, il gancio ci mostrerà la posizione dello scroll ad ogni passaggio dello scroll.
Possiamo creare hook personalizzati per fare qualsiasi cosa possiamo immaginare nella nostra app. Come puoi vedere, dobbiamo semplicemente usare l'hook React integrato per eseguire alcune funzioni. Possiamo anche utilizzare librerie di terze parti per creare hook personalizzati, ma in tal caso, dovremo installare quella libreria per poter utilizzare l'hook.
Conclusione
In questo tutorial, abbiamo dato una buona occhiata ad alcuni utili hook React che utilizzerai nella maggior parte delle tue applicazioni. Abbiamo esaminato ciò che presentano e come utilizzarli nella tua applicazione. Abbiamo anche esaminato diversi esempi di codice per aiutarti a comprendere questi hook e applicarli alla tua applicazione.
Ti incoraggio a provare questi hook nella tua applicazione per capirli di più.
Risorse da The React Docs
- Domande frequenti sui ganci
- Kit di strumenti Redux
- Usando il gancio di stato
- Usando l'Effetto Hook
- Riferimento API Hooks
- Reagire Redux Hooks
- Reagire ai ganci del router