Cârlige React utile pe care le puteți folosi în proiectele dvs
Publicat: 2022-03-10Cârligele sunt pur și simplu funcții care vă permit să vă conectați sau să utilizați funcțiile React. Acestea au fost introduse la React Conf 2018 pentru a aborda trei probleme majore ale componentelor clasei: iadul de înveliș, componente uriașe și clase confuze. Cârligele dau putere componentelor funcționale React, făcând posibilă dezvoltarea unei întregi aplicații cu aceasta.
Problemele menționate mai sus ale componentelor clasei sunt conectate și rezolvarea una fără cealaltă ar putea introduce alte probleme. Din fericire, cârligele au rezolvat toate problemele simplu și eficient, creând spațiu pentru funcții mai interesante în React. Hook-urile nu înlocuiesc conceptele și clasele React deja existente, ci doar oferă un API pentru a le accesa direct.
Echipa React a introdus mai multe cârlige în React 16.8. Cu toate acestea, puteți utiliza și cârlige de la furnizori terți în aplicația dvs. sau chiar puteți crea un cârlig personalizat. În acest tutorial, vom arunca o privire la câteva cârlige utile în React și cum să le folosim. Vom parcurge câteva exemple de cod pentru fiecare cârlig și vom explora, de asemenea, cum ați crea un cârlig personalizat.
Notă: Acest tutorial necesită o înțelegere de bază a Javascript (ES6+) și React.
Motivația din spatele cârligelor
După cum am menționat mai devreme, cârligele au fost create pentru a rezolva trei probleme: iadul de ambalare, componente uriașe și clase confuze. Să aruncăm o privire la fiecare dintre acestea mai detaliat.
Wrapper Hell
Aplicațiile complexe construite cu componente de clasă rulează cu ușurință în iadul de pachete. Dacă examinați aplicația în React Dev Tools, veți observa componente profund imbricate. Acest lucru face foarte dificil să lucrați cu componentele sau să le depanați. Deși aceste probleme ar putea fi rezolvate cu componente de ordin mai înalt și elemente de recuzită de randare , ele necesită să modificați puțin codul. Acest lucru ar putea duce la confuzie într-o aplicație complexă.
Cârligele sunt ușor de partajat, nu trebuie să vă modificați componentele înainte de a reutiliza logica.
Un bun exemplu în acest sens este utilizarea Redux connect
Higher Order Component (HOC) pentru a vă abona la magazinul Redux. Ca toate HOC, pentru a utiliza connect HOC, trebuie să exportați componenta alături de funcțiile de ordin superior definite. În cazul connect
, vom avea ceva din această formă.
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
Unde mapStateToProps
și mapDispatchToProps
sunt funcții care trebuie definite.
În timp ce în era Hooks, se poate obține cu ușurință același rezultat în mod ordonat și succint utilizând Redux useSelector
și useDispatch
hooks.
Componente uriașe
Componentele clasei conțin, de obicei, efecte secundare și logică cu stare. Pe măsură ce aplicația crește în complexitate, este obișnuit ca componenta să devină dezordonată și confuză. Acest lucru se datorează faptului că se așteaptă ca efectele secundare să fie organizate prin metode ciclului de viață, mai degrabă decât prin funcționalitate. Deși este posibil să împărțiți componentele și să le faceți mai simple, aceasta introduce adesea un nivel mai ridicat de abstractizare.
Cârligele organizează efectele secundare în funcție de funcționalitate și este posibil să împărțiți o componentă în bucăți în funcție de funcționalitate.
Clase confuze
Clasele sunt în general un concept mai dificil decât funcțiile. Componentele React bazate pe clasă sunt detaliate și puțin dificile pentru începători. Dacă sunteți nou în Javascript, puteți găsi funcții mai ușor de început din cauza sintaxei lor ușoare în comparație cu clasele. Sintaxa ar putea fi confuză; uneori, este posibil să uitați legarea unui handler de evenimente care ar putea rupe codul.
React rezolvă această problemă cu componente funcționale și cârlige, permițând dezvoltatorilor să se concentreze mai degrabă pe proiect decât pe sintaxa codului.
De exemplu, următoarele două componente React vor da exact același rezultat.
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> ); }
Primul exemplu este o componentă bazată pe clasă, în timp ce al doilea este o componentă funcțională. Deși acesta este un exemplu simplu, observați cât de fals este primul exemplu în comparație cu al doilea.
Convenția și regulile Hooks
Înainte de a explora diferitele cârlige, ar putea fi util să aruncați o privire asupra convenției și regulilor care se aplică acestora. Iată câteva dintre regulile care se aplică cârligelor.
- Convenția de denumire a cârligelor ar trebui să înceapă cu prefixul
use
. Deci, putem aveauseState
,useEffect
etc. Dacă utilizați editori de cod modern precum Atom și VSCode, pluginul ESLint ar putea fi o caracteristică foarte utilă pentru cârligele React. Pluginul oferă avertismente utile și indicii despre cele mai bune practici. - Hook-urile trebuie apelate la nivelul superior al unei componente, înainte de instrucțiunea return. Ele nu pot fi apelate într-o instrucțiune condiționată, buclă sau funcții imbricate.
- Hook-urile trebuie apelate dintr-o funcție React (în interiorul unei componente React sau al unui alt hook). Nu ar trebui apelat dintr-o funcție Vanilla JS.
useState
Hook
Cârligul useState
este cel mai simplu și util cârlig React. Ca și alte cârlige încorporate, acest cârlig trebuie importat din react
pentru a fi utilizat în aplicația noastră.
import {useState} from 'react'
Pentru a inițializa starea, trebuie să declarăm atât starea, cât și funcția ei de actualizare și să transmitem o valoare inițială.
const [state, updaterFn] = useState('')
Suntem liberi să ne apelăm starea și funcția de actualizare orice dorim, dar prin convenție, primul element al matricei va fi starea noastră, în timp ce al doilea element va fi funcția de actualizare. Este o practică obișnuită să prefixăm funcția noastră de actualizare cu setul de prefixe urmat de numele statului nostru în formă de caz cămilă.
De exemplu, să setăm o stare pentru a păstra valorile de numărare.
const [count, setCount] = useState(0)
Observați că valoarea inițială a stării noastre de count
este setată la 0
și nu la un șir gol. Cu alte cuvinte, ne putem inițializa starea la orice fel de variabile JavaScript, și anume număr, șir, boolean, matrice, obiect și chiar BigInt. Există o diferență clară între setarea stărilor cu ajutorul hook- useState
și stările componentelor bazate pe clasă. Este de remarcat faptul că hook-ul useState
returnează o matrice, cunoscută și sub numele de variabile de stare, iar în exemplul de mai sus, am destructurat matricea în state
și funcția de updater
.
Componente de redare
Setarea stărilor cu ajutorul cârligului useState
face ca componenta corespunzătoare să fie redată din nou. Cu toate acestea, acest lucru se întâmplă numai dacă React detectează o diferență între starea anterioară sau veche și starea nouă. React face compararea stărilor folosind algoritmul Javascript Object.is
.
Setarea statelor cu useState
Starea noastră de count
poate fi setată la noi valori de stare prin simpla transmitere a noii valori la funcția de actualizare setCount
, după cum urmează setCount(newValue)
.
Această metodă funcționează atunci când nu dorim să facem referință la valoarea stării anterioare. Dacă dorim să facem asta, trebuie să transmitem o funcție funcției setCount
.
Presupunând că dorim să adăugăm 5 la variabila noastră de count
oricând se face clic pe un buton, am putea face următoarele.
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
În codul de mai sus, am importat mai întâi cârligul useState
din react
și apoi am inițializat starea de count
cu o valoare implicită de 0. Am creat un handler onClick
pentru a incrementa valoarea count
cu 5 ori de câte ori este apăsat butonul. Apoi am afișat rezultatul într-o etichetă h1
.
Setarea matricelor și stărilor obiectelor
Stările pentru matrice și obiecte pot fi setate aproape în același mod ca și alte tipuri de date. Cu toate acestea, dacă dorim să păstrăm valorile deja existente, trebuie să folosim operatorul de răspândire ES6 atunci când setăm stările.
Operatorul de răspândire în Javascript este folosit pentru a crea un obiect nou dintr-un obiect deja existent. Acest lucru este util aici deoarece React
compară stările cu operația Object.is
și apoi redă din nou în consecință.
Să luăm în considerare codul de mai jos pentru setarea stărilor la clic pe buton.
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
În codul de mai sus, am creat două stări arr
și obj
și le-am inițializat la niște valori de matrice și, respectiv, obiect. Am creat apoi handlere onClick
numite handleArrClick
și handleObjClick
pentru a seta stările matricei și respectiv obiectului. Când handleArrClick
declanșează, apelăm setArr
și folosim operatorul de răspândire ES6 pentru a răspândi valorile matricei deja existente și pentru a adăuga newArr
la acesta.
Am făcut același lucru pentru handler- handleObjClick
. Aici am numit setObj
, am răspândit valorile obiectelor existente folosind operatorul de răspândire ES6 și am actualizat valorile name
și age
.
Natura useState
a statului de utilizare
După cum am văzut deja, setăm stări cu useState
trecând o nouă valoare funcției de actualizare. Dacă actualizatorul este apelat de mai multe ori, noile valori vor fi adăugate la o coadă și redarea se face în consecință folosind comparația JavaScript Object.is
.
Stările sunt actualizate asincron. Aceasta înseamnă că noua stare este mai întâi adăugată la o stare în așteptare și, ulterior, starea este actualizată. Deci, este posibil să obțineți în continuare valoarea veche a stării dacă accesați starea imediat ce este setată.
Să luăm în considerare următorul exemplu pentru a observa acest comportament.
În codul de mai sus, am creat o stare de count
folosind cârligul useState
. Apoi am creat un handler onClick
pentru a incrementa starea de count
ori de câte ori este apăsat butonul. Observați că, deși starea de count
a crescut, așa cum este afișată în eticheta h2
, starea anterioară este încă înregistrată în consolă. Acest lucru se datorează naturii asincrone a cârligului.
Dacă dorim să obținem noua stare, o putem gestiona într-un mod similar în care am gestiona funcțiile asincrone. Iată o modalitate de a face asta.
Aici, am stocat newCountValue
creat pentru a stoca valoarea actualizată a numărului și apoi a setat starea de count
cu valoarea actualizată. Apoi, am înregistrat valoarea actualizată a numărului în consolă.
Cârligul useEffect
useEffect
este un alt cârlig React important folosit în majoritatea proiectelor. Face un lucru similar cu metodele ciclului de viață componentDidMount
, componentWillUnmount
și componentDidUpdate
ale componentei bazate pe clasă. useEffect
ne oferă posibilitatea de a scrie coduri imperative care pot avea efecte secundare asupra aplicației. Exemple de astfel de efecte includ înregistrarea în jurnal, abonamentele, mutațiile etc.
Utilizatorul poate decide când va rula useEffect
, totuși, dacă nu este setat, efectele secundare vor rula la fiecare randare sau redare.
Luați în considerare exemplul de mai jos.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }
În codul de mai sus, pur și simplu am conectat count
în useEffect
. Aceasta va rula după fiecare randare a componentei.
Uneori, putem dori să rulăm cârligul o dată (pe suport) în componenta noastră. Putem realiza acest lucru furnizând un al doilea parametru pentru useEffect
hook.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }
Hook-ul useEffect
are doi parametri, primul parametru este funcția pe care vrem să o rulăm, în timp ce al doilea parametru este o matrice de dependențe. Dacă al doilea parametru nu este furnizat, cârligul va funcționa continuu.
Trecând un parantez pătrat gol celui de-al doilea parametru al cârligului, îi dăm instrucțiuni React să ruleze cârligul useEffect
o singură dată, pe montură. Aceasta va afișa valoarea 1
în eticheta h1
deoarece numărul va fi actualizat o dată, de la 0 la 1, când componenta se montează.
De asemenea, am putea face efectul nostru secundar să ruleze ori de câte ori unele valori dependente se schimbă. Acest lucru se poate face prin trecerea acestor valori în lista de dependențe.
De exemplu, am putea face ca useEffect
să ruleze ori de câte ori count
se modifică, după cum urmează.
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
de mai sus va rula atunci când oricare dintre aceste două condiții este îndeplinită.
- La montare — după ce componenta este redată.
- Când valoarea
count
se schimbă.
La montare, expresia console.log
va rula și count
de jurnal la 0. Odată actualizat count
, a doua condiție este îndeplinită, astfel încât useEffect
rulează din nou, aceasta va continua de fiecare dată când se face clic pe butonul.
Odată ce furnizăm al doilea argument pentru useEffect
, este de așteptat să îi transmitem toate dependențele. Dacă aveți ESLINT
instalat, va afișa o eroare lint dacă vreo dependență nu este trecută în lista de parametri. Acest lucru ar putea face și efectul secundar să se comporte neașteptat, mai ales dacă depinde de parametrii care nu sunt trecuți.
Curățarea efectului
useEffect
ne permite, de asemenea, să curățăm resursele înainte ca componenta să se demonteze. Acest lucru poate fi necesar pentru a preveni scurgerile de memorie și pentru a face aplicația mai eficientă. Pentru a face acest lucru, vom returna funcția de curățare la capătul cârligului.
useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })
Cârligul useEffect
de mai sus va fi mounted
când componenta este montată. Demontarea... curățarea aici va fi înregistrată când componenta se demontează. Acest lucru se poate întâmpla atunci când componenta este eliminată din interfața de utilizare.
Procesul de curățare urmează de obicei formularul de mai jos.
useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })
Deși este posibil să nu găsiți atât de multe cazuri de utilizare pentru abonamentele useEffect
, este util atunci când aveți de-a face cu abonamente și cronometre. În special, atunci când aveți de-a face cu socket-uri web, poate fi necesar să vă dezabonați de la rețea pentru a economisi resurse și pentru a îmbunătăți performanța atunci când componenta se demontează.
Preluarea și recuperarea datelor cu useEffect
Unul dintre cele mai frecvente cazuri de utilizare a cârligului useEffect
este preluarea și preluarea prealabilă a datelor dintr-un API.
Pentru a ilustra acest lucru, vom folosi datele utilizator false pe care le-am creat din JSONPlaceholder
pentru a prelua date cu ajutorul 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> ); }
În codul de mai sus, am creat un stat de users
folosind cârligul useState
. Apoi am preluat date dintr-un API folosind Axios. Acesta este un proces asincron și așa că am folosit funcția async/wait, am fi putut folosi și punctul, apoi sintaxa. Deoarece am preluat o listă de utilizatori, pur și simplu am mapat prin ea pentru a afișa datele.
Observați că am trecut un parametru gol cârligului. Acest lucru asigură că este apelat o singură dată când se montează componenta.
De asemenea, putem recupera datele atunci când unele condiții se schimbă. Vom arăta acest lucru în codul de mai jos.
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> ); }
Aici am creat două cârlige useEffect
. În primul, am folosit punctul, apoi sintaxa pentru a obține toți utilizatorii din API-ul nostru. Acest lucru este necesar pentru a determina numărul de utilizatori.
Apoi am creat un alt cârlig useEffect
pentru a obține un utilizator pe baza id
-ului. Acest useEffect
va recupera datele ori de câte ori id-ul se schimbă. Pentru a ne asigura acest lucru, am trecut id
-ul în lista de dependențe.
Apoi, am creat funcții pentru a actualiza valoarea id
-ului nostru ori de câte ori se face clic pe butoane. Odată ce valoarea id
-ului se schimbă, useEffect
va rula din nou și va recăpăta datele.
Dacă dorim, putem chiar să curățăm sau să anulăm jetonul bazat pe promisiuni în Axios, am putea face asta cu metoda de curățare discutată mai sus.
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]);
Aici, am transmis jetonul Axios ca al doilea parametru către axios.get
. Când componenta se demontează, am anulat apoi abonamentul apelând metoda de anulare a obiectului sursă.
useReducer
Hook
Cârligul useReducer
este un cârlig React foarte util care face un lucru similar cu cârligul useState
. Conform documentației React, acest cârlig ar trebui utilizat pentru a gestiona o logică mai complexă decât cârligul useState
. Este demn de remarcat faptul că cârligul useState
este implementat intern cu cârligul useReducer.
Cârligul ia un reductor ca argument și poate lua opțional starea inițială și o funcție init ca argumente.
const [state, dispatch] = useReducer(reducer, initialState, init)
Aici, init
este o funcție și este folosită ori de câte ori dorim să creăm leneș starea inițială.
Să ne uităm la cum să implementăm cârligul useReducer
prin crearea unei aplicații simple de făcut, așa cum se arată în sandbox-ul de mai jos.
În primul rând, ar trebui să ne creăm reductorul pentru a menține statele.
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;
Am creat trei constante corespunzătoare tipurilor noastre de acțiuni. Am fi putut folosi direct șiruri de caractere, dar această metodă este de preferat pentru a evita greșelile de scriere.
Apoi am creat funcția noastră de reducere. Ca și în Redux
, reductorul trebuie să preia starea și obiectul de acțiune. Dar, spre deosebire de Redux, nu trebuie să ne inițializam reductorul aici.
În plus, pentru o mulțime de cazuri de utilizare a managementului de stat, un useReducer
împreună cu dispatch
expusă prin context poate permite o aplicație mai mare să declanșeze acțiuni, să actualizeze state
și să o asculte.
Apoi am folosit instrucțiunile switch
pentru a verifica tipul de acțiune transmis de utilizator. Dacă tipul de acțiune este ADD_TODO
, vrem să trecem o nouă de făcut și dacă este REMOVE_TODO
, dorim să filtram sarcinile de făcut și să o eliminăm pe cea care corespunde id
-ului transmis de utilizator. Dacă este COMPLETE_TODO
, vrem să mapăm sarcinile de făcut și să comutăm pe cea cu id
-ul transmis de utilizator.
Aici este fișierul App.js
unde am implementat 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> ); }
Aici, am creat un formular care conține un element de intrare, pentru a colecta intrarea utilizatorului și un buton pentru a declanșa acțiunea. Când formularul este trimis, am trimis o acțiune de tip ADD_TODO
, pasând un nou id și un text de făcut. Am creat un nou id prin creșterea valorii id-ului anterioară cu 1. Apoi am șters caseta de text de intrare. Pentru a șterge și a finaliza activitățile de făcut, pur și simplu am trimis acțiunile corespunzătoare. Acestea au fost deja implementate în reductor așa cum se arată mai sus.
Cu toate acestea, magia se întâmplă deoarece folosim cârligul useReducer
. Acest cârlig acceptă reductorul și starea inițială și returnează starea și funcția de expediere. Aici, funcția de expediere are același scop ca și funcția de setare pentru cârligul useState
și o putem numi orice dorim în loc de dispatch
.
Pentru a afișa elementele de făcut, pur și simplu am mapat prin lista de sarcini returnate în obiectul nostru de stare, așa cum se arată în codul de mai sus.
Aceasta arată puterea cârligului useReducer
. Am putea realiza această funcționalitate și cu cârligul useState
, dar după cum puteți vedea din exemplul de mai sus, cârligul useReducer
ne-a ajutat să păstrăm lucrurile mai ordonate. useReducer
este adesea benefic atunci când obiectul de stare este o structură complexă și este actualizat în moduri diferite față de o simplă înlocuire a valorii. De asemenea, odată ce aceste funcții de actualizare devin mai complicate, useReducer
face ușor să păstrați toată această complexitate într-o funcție de reducere (care este o funcție JS pură), făcând foarte ușor să scrieți teste numai pentru funcția de reducere.
Am fi putut, de asemenea, să trecem al treilea argument cârligului useReducer
pentru a crea starea inițială leneș. Aceasta înseamnă că am putea calcula starea inițială într-o funcție init
.
De exemplu, am putea crea o funcție init
după cum urmează:
const initFunc = () => [ { id: id, text: "First Item", completed: false } ]
și apoi treceți-l la cârligul nostru useReducer
.
const [state, dispatch] = useReducer(reducer, initialState, initFunc)
Dacă facem acest lucru, initFunc
va suprascrie initialState
pe care l-am furnizat și starea inițială va fi calculată leneș.
useContext
Hook
API-ul React Context oferă o modalitate de a partaja stări sau date în arborele componentelor React. API-ul a fost disponibil în React, ca o caracteristică experimentală, de ceva vreme, dar a devenit sigur de utilizat în React 16.3.0. API-ul ușurează partajarea datelor între componente, eliminând în același timp găurirea suportului.
În timp ce puteți aplica contextul React întregii aplicații, este, de asemenea, posibil să îl aplicați unei părți a aplicației.
Pentru a utiliza cârligul, trebuie mai întâi să creați un context folosind React.createContext
și acest context poate fi apoi transmis cârligului.
Pentru a demonstra utilizarea cârligului useContext
, să creăm o aplicație simplă care va crește dimensiunea fontului în aplicația noastră.
Să ne creăm contextul în fișierul context.js
.
import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;
Aici, am creat un context și i-am transmis o valoare inițială de 16
, apoi am exportat contextul. În continuare, să conectăm contextul nostru la aplicația noastră.
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;
În codul de mai sus, am împachetat întregul nostru arbore de componente cu FontSizeContext.Provider
și am transmis size
valorii sale prop. Aici, size
este o stare creată cu ajutorul cârligului useState
. Acest lucru ne permite să schimbăm valoarea prop ori de câte ori se schimbă starea size
. Prin împachetarea întregii componente cu Provider
, putem accesa contextul oriunde în aplicația noastră.
De exemplu, am accesat contextul în <PageOne />
și <PageTwo />
. Ca urmare, dimensiunea fontului va crește în aceste două componente atunci când o creștem din fișierul App.js
Putem crește sau micșora dimensiunea fontului de la butoane așa cum se arată mai sus și, odată ce o facem, dimensiunea fontului se schimbă în întreaga aplicație.
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;
Aici, am accesat contextul folosind cârligul useContext
din componenta noastră PageOne
. Apoi am folosit acest context pentru a seta proprietatea font-size. O procedură similară se aplică fișierului PageTwo.js
.
Temele sau alte configurații de ordin superior la nivel de aplicație sunt candidați buni pentru contexte.
Folosind useContext
și useReducer
Când este utilizat cu cârligul useReducer
, useContext
ne permite să ne creăm propriul sistem de management al statului. Putem crea state globale și le putem gestiona cu ușurință în aplicația noastră.
Să îmbunătățim aplicația noastră de făcut folosind API-ul context.
Ca de obicei, trebuie să creăm un todoContext
în fișierul todoContext.js
.
import { createContext } from "react"; const initialState = []; export default createContext(initialState);
Aici am creat contextul, trecând o valoare inițială a unui tablou gol. Apoi am exportat contextul.
Să refactorăm fișierul App.js
, separând lista de sarcini și elementele.
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> ); }
Aici, am împachetat fișierul nostru App.js
cu TodoContext.Provider
, apoi i-am transmis valorile returnate ale todoReducer
-ului nostru. Acest lucru face ca starea reductorului și funcția de dispatch
să fie accesibile în întreaga aplicație.
Apoi am separat afișarea de sarcini într-o componentă TodoList
. Am făcut acest lucru fără găurire, datorită API-ului Context. Să aruncăm o privire la fișierul 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;
Folosind destructurarea matricei, putem accesa starea (lăsând funcția de expediere) din context folosind cârligul useContext
. Putem apoi să mapam prin stat și să afișăm elementele de făcut. Încă am extras acest lucru într-o componentă Todo
. Funcția de hartă ES6+ ne cere să transmitem o cheie unică și, deoarece avem nevoie de acțiunea specifică, o trecem și alături.
Să aruncăm o privire la componenta 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;
Din nou folosind destructurarea matricei, am accesat funcția de expediere din context. Acest lucru ne permite să definim funcția completeTodo
și removeTodo
așa cum sa discutat deja în secțiunea useReducer
. Cu tot todo
transmis de la todoList.js
, putem afișa un element de făcut. De asemenea, îl putem marca ca finalizat și putem elimina sarcinile de făcut după cum considerăm potrivit.
De asemenea, este posibil să imbricați mai mult de un furnizor de context în rădăcina aplicației noastre. Aceasta înseamnă că putem folosi mai mult de un context pentru a îndeplini diferite funcții într-o aplicație.
Pentru a demonstra acest lucru, să adăugăm o tematică la exemplul de rezolvat.
Iată ce vom construi.
Din nou, trebuie să creăm themeContext
. Pentru a face acest lucru, creați un fișier themeContext.js
și adăugați următoarele coduri.
import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);
Aici, am creat un context și am trecut colors.light
ca valoare inițială. Să definim culorile cu această proprietate în fișierul colors.js
.
const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;
În codul de mai sus, am creat un obiect de colors
care conține proprietăți de lumină și întuneric. Fiecare proprietate are backgroundColor
și obiect color
.
Apoi, creăm themeReducer
pentru a gestiona stările temei.
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;
Ca toate reductoarele, themeReducer
preia starea și acțiunea. Apoi folosește instrucțiunea switch
pentru a determina acțiunea curentă. Dacă este de tip LIGHT
, pur și simplu atribuim recuzită Colors.light
și dacă este de tip DARK
, afișăm recuzită Colors.dark
. Am fi putut face acest lucru cu ușurință cu cârligul useState
, dar alegem useReducer
pentru a conduce punctul acasă.
După ce am configurat themeReducer
, îl putem integra în fișierul nostru 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> ); }
În codul de mai sus, am adăugat câteva lucruri la aplicația noastră deja existentă. Am început prin importul ThemeContext
, themeReducer
, ThemeToggler
și 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. Să aruncăm o privire.
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]);
Am folosit axios
și metoda async/await
wait în primul useEffect
și apoi punctul, apoi sintaxa în al doilea. Aceste două abordări funcționează în același mod.
Apoi, folosind datele angajaților pe care le-am obținut de mai sus, să calculăm variabilele de relief:
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]);
Acesta este un calcul destul de complex și așa că a trebuit să-l înfășurăm într-un cârlig useMemo
pentru a-l memoriza sau optimiza. Memorându-l în acest fel, se va asigura că calculul nu va fi recalculat dacă am încercat să accesăm din nou același angajat.
În plus, folosind valorile scutirii fiscale obținute mai sus, dorim să calculăm PAYE și venitul după 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]);
Am efectuat calculul impozitului (un calcul destul de complex) folosind variabilele fiscale calculate mai sus și apoi l-am memorat cu cârligul useMemo
.
Codul complet este disponibil aici.
Aceasta urmează procedura de calcul a taxelor prezentată aici. Mai întâi am calculat scutirea fiscală luând în considerare venitul, numărul de copii și numărul de rude aflate în întreținere. Apoi, am înmulțit venitul impozabil cu cotele PIT în pași. Deși calculul în cauză nu este în întregime necesar pentru acest tutorial, acesta este furnizat pentru a ne arăta de ce poate fi necesar să useMemo
. Acesta este, de asemenea, un calcul destul de complex și, prin urmare, poate fi necesar să-l memorăm cu useMemo
, așa cum se arată mai sus.
După ce am calculat valorile, pur și simplu am afișat rezultatul.
Rețineți următoarele despre cârligul useMemo
.
-
useMemo
ar trebui utilizat numai atunci când este necesar pentru optimizarea calculului. Cu alte cuvinte, atunci când recalcularea este costisitoare. - Este recomandabil să scrieți mai întâi calculul fără memorare și să îl memorați numai dacă cauzează probleme de performanță.
- Utilizarea inutilă și irelevantă a cârligului
useMemo
poate chiar agrava problemele de performanță. - Uneori, prea multă memorare poate cauza probleme de performanță.
useCallback
Hook
useCallback
servește același scop ca useMemo
, dar returnează un apel invers memorat în loc de o valoare memorată. Cu alte cuvinte, useCallback
este același cu transmiterea useMemo
fără un apel de funcție.
De exemplu, luați în considerare următoarele coduri de mai jos.
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
În exemplul de mai sus, atât memoResult
, cât și callbackResult
vor da aceeași valoare de 12
. Aici, useCallback
va returna o valoare memorată. Cu toate acestea, am putea, de asemenea, să-l facem să returneze un apel invers memorat, trecându-l ca funcție.
useCallback
de mai jos va returna un apel invers memorat.
... const callbackResult = useCallback(() => a + b, [a, b]) ...
Putem apoi declanșa apelul invers atunci când este efectuată o acțiune sau într-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
În codul de mai sus, am definit o funcție de apel invers folosind cârligul useCallback
. Apoi am apelat înapoi într-un cârlig useEffect
atunci când componenta se montează și, de asemenea, când se face clic pe un buton.
Atât useEffect
cât și clicul butonului dau același rezultat.
Rețineți că conceptele, ceea ce se poate face și nu se aplică cârligului useMemo
se aplică și cârligului useCallback
. Putem recrea exemplul useMemo
cu useCallback
.
useRef
Hook
useRef
returnează un obiect care poate persista într-o aplicație. Cârligul are o singură proprietate, current
, și îi putem transmite cu ușurință un argument.
Acesta servește același scop cu createRef
folosit în componentele bazate pe clasă. Putem crea o referință cu acest cârlig după cum urmează:
const newRef = useRef('')
Aici am creat un nou ref numit newRef
și i-am transmis un șir gol.
Acest cârlig este folosit în principal în două scopuri:
- Accesarea sau manipularea DOM-ului și
- Stocarea stărilor mutabile - acest lucru este util atunci când nu dorim ca componenta să fie redată din nou atunci când o valoare se schimbă.
Manipularea DOM
Când este transmis unui element DOM, obiectul ref indică acel element și poate fi folosit pentru a accesa atributele și proprietățile sale DOM.
Iată un exemplu foarte simplu pentru a demonstra acest concept.
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
În exemplul de mai sus, am definit headingRef
folosind cârligul useRef
trecând un șir gol. Apoi setăm ref în eticheta h1
trecând ref = {headingRef}
. Prin setarea acestui ref, am cerut headingRef
să indice elementul nostru h1
. Aceasta înseamnă că putem accesa proprietățile elementului nostru h1
din ref.
Pentru a vedea acest lucru, dacă verificăm valoarea console.log(headingRef)
, vom obține {current: HTMLHeadingElement}
sau {current: h1}
și putem evalua toate proprietățile sau atributele elementului. Un lucru similar se aplică oricărui alt element HTML.
De exemplu, am putea face textul italic atunci când componenta se montează.
useEffect(() => { headingRef.current.style.font; }, []);
Putem chiar schimba textul cu altceva.
... headingRef.current.innerHTML = "A Changed H1 Element"; ...
Putem chiar schimba și culoarea de fundal a containerului părinte.
... headingRef.current.parentNode.style.backgroundColor = "red"; ...
Orice fel de manipulare DOM poate fi făcută aici. Observați că headingRef.current
poate fi citit în același mod ca document.querySelector('.topheading')
.
Un caz de utilizare interesant al cârligului useRef
în manipularea elementului DOM este focalizarea cursorului pe elementul de intrare. Să alergăm repede prin el.
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
În codul de mai sus, am creat inputRef
folosind cârligul useRef
și apoi i-am cerut să indice elementul de intrare. Apoi am pus cursorul să se concentreze pe referința de intrare când componenta se încarcă și când se face clic pe butonul folosind inputRef.current.focus()
. Acest lucru este posibil deoarece focus()
este un atribut al elementelor de intrare și astfel reful va putea evalua metodele.
Refurile create într-o componentă părinte pot fi evaluate la componenta fii prin transmiterea acesteia folosind React.forwardRef()
. Să aruncăm o privire.
Să creăm mai întâi o altă componentă NewInput.js
și să îi adăugăm următoarele coduri.
import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;
Această componentă acceptă props
și ref
. Am transmis referința la prop ref și props.val
la propul său substituent. Componentele obișnuite React nu au un atribut ref
. Acest atribut este disponibil numai atunci când îl împachetăm cu React.forwardRef
, așa cum se arată mai sus.
Apoi putem apela cu ușurință acest lucru în componenta părinte.
... <NewInput val="Just an example" ref={inputRef} /> ...
Stocarea Statelor Mutabile
Refurile nu sunt folosite doar pentru a manipula elementele DOM, ele pot fi, de asemenea, folosite pentru a stoca valori modificabile fără a reda întreaga componentă.
Următorul exemplu va detecta de câte ori se face clic pe un buton fără a reda componenta.
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> ); }
În codul de mai sus, am incrementat countRef
atunci când este apăsat butonul și apoi l-am conectat la consolă. Deși valoarea este incrementată așa cum se arată în consolă, nu vom putea vedea nicio modificare dacă încercăm să o evaluăm direct în componenta noastră. Se va actualiza în componentă numai atunci când se redă din nou.
Rețineți că, în timp ce useState
este asincron, useRef
este sincron. Cu alte cuvinte, valoarea este disponibilă imediat după ce este actualizată.
Cârligul useLayoutEffect
Ca și cârligul useEffect
, useLayoutEffect
este apelat după ce componenta este montată și randată. Acest cârlig se declanșează după mutația DOM și o face sincron. În afară de a fi apelat sincron după mutația DOM, useLayoutEffect
face același lucru ca useEffect
.
useLayoutEffect
ar trebui utilizat numai pentru efectuarea mutației DOM sau a măsurătorilor legate de DOM, în caz contrar, ar trebui să utilizați cârligul useEffect
. Utilizarea cârligului useEffect
pentru funcțiile de mutație DOM poate cauza unele probleme de performanță, cum ar fi pâlpâirea, dar useLayoutEffect
le gestionează perfect, deoarece rulează după ce au apărut mutațiile.
Să aruncăm o privire la câteva exemple pentru a demonstra acest concept.
- Vom primi lățimea și înălțimea ferestrei la redimensionare.
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
În codul de mai sus, am creat o stare windowSize
cu proprietăți de lățime și înălțime. Apoi setăm starea la lățimea și respectiv înălțimea ferestrei curente când fereastra este redimensionată. Am curățat și codul când se demontează. Procesul de curățare este esențial în useLayoutEffect
pentru a curăța manipularea DOM și pentru a îmbunătăți eficiența.
- Să estompăm un text cu
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> ); }
Am folosit useRef
și useLayoutEffect
împreună în codul de mai sus. Am creat mai întâi o referință, paragraphRef
pentru a indica paragraful nostru. Apoi am creat un ascultător de evenimente la clic pentru a monitoriza când se face clic pe paragraful și apoi l-am estompat folosind proprietățile de stil pe care le-am definit. În cele din urmă, am curățat ascultătorul de evenimente folosind removeEventListener
.
useDispatch
și useSelector
useDispatch
este un hook Redux pentru trimiterea (declanșarea) acțiunilor într-o aplicație. Acesta ia un obiect de acțiune ca argument și invocă acțiunea. useDispatch
este echivalența cârligului cu mapDispatchToProps
.
Pe de altă parte, useSelector
este un cârlig Redux pentru evaluarea stărilor Redux. Este nevoie de o funcție pentru a selecta reductorul Redux exact din magazin și apoi returnează stările corespunzătoare.
Odată ce magazinul nostru Redux este conectat la o aplicație React prin furnizorul Redux, putem invoca acțiunile cu useDispatch
și putem accesa stările cu useSelector
. Fiecare acțiune și stare Redux poate fi evaluată cu aceste două cârlige.
Rețineți că aceste state sunt livrate cu React Redux (un pachet care facilitează evaluarea magazinului Redux într-o aplicație React). Ele nu sunt disponibile în biblioteca de bază Redux.
Aceste cârlige sunt foarte simplu de utilizat. În primul rând, trebuie să declarăm funcția de expediere și apoi să o declanșăm.
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
În codul de mai sus, am importat useDispatch
și useSelector
din react-redux
. Apoi, într-un cârlig useEffect
, am trimis acțiunea. Am putea defini acțiunea într-un alt fișier și apoi o numim aici sau am putea-o defini direct așa cum se arată în apelul useEffect
.
Odată ce vom trimite acțiunile, statele noastre vor fi disponibile. Apoi putem prelua starea utilizând cârligul useSelector
, așa cum se arată. Stările pot fi folosite în același mod în care am folosi stările din cârligul useState
.
Să aruncăm o privire la un exemplu pentru a demonstra aceste două cârlige.
Pentru a demonstra acest concept, trebuie să creăm un magazin Redux, un reductor și acțiuni. Pentru a simplifica lucrurile aici, vom folosi biblioteca Redux Toolkit cu baza noastră de date falsă de la JSONPlaceholder.
Trebuie să instalăm următoarele pachete pentru a începe. Rulați următoarele comenzi bash.
npm i redux @reduxjs/toolkit react-redux axios
Mai întâi, să creăm employeesSlice.js
pentru a gestiona reductorul și acțiunea pentru API-ul angajaților noștri.
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;
Aceasta este configurarea standard pentru setul de instrumente Redux. Am folosit createAsyncThunk
pentru a accesa middleware-ul Thunk
pentru a efectua acțiuni asincrone. Acest lucru ne-a permis să preluăm lista de angajați din API. Apoi am creat employeesSlice
și am returnat „încărcare”, „eroare” și datele angajaților în funcție de tipurile de acțiuni.
Setul de instrumente Redux facilitează, de asemenea, configurarea magazinului. Aici este magazinul.
import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;
Aici, am folosit combineReducers
pentru a combina reductoarele și funcția configureStore
furnizată de setul de instrumente Redux pentru a configura magazinul.
Să continuăm să folosim acest lucru în aplicația noastră.
În primul rând, trebuie să conectăm Redux la aplicația noastră React. În mod ideal, acest lucru ar trebui făcut la rădăcina aplicației noastre. Îmi place să o fac în fișierul 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 );
Aici, am importat magazinul creat mai sus și, de asemenea, Provider
din react-redux
.
Apoi, am împachetat întreaga aplicație cu funcția Provider
, trecându-i magazinul. Acest lucru face ca magazinul să fie accesibil prin intermediul aplicației noastre.
Apoi putem continua să folosim cârligele useDispatch
și useSelector
pentru a prelua datele.
Să facem asta în fișierul nostru 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> ); }
În codul de mai sus, am folosit cârligul useDispatch
pentru a invoca acțiunea fetchEmployees
creată în fișierul employeesSlice.js
. Acest lucru face ca angajații să declare să fie disponibili în aplicația noastră. Apoi, am folosit cârligul useSelector
pentru a obține stările. După aceea, am afișat rezultatele prin maparea employees
.
Cârligul useHistory
Navigarea este foarte importantă într-o aplicație React. Deși puteți realiza acest lucru în câteva moduri, React Router oferă o modalitate simplă, eficientă și populară de a realiza rutarea dinamică într-o aplicație React. În plus, React Router oferă câteva cârlige pentru evaluarea stării routerului și efectuarea navigării în browser, dar pentru a le utiliza, trebuie mai întâi să configurați aplicația în mod corespunzător.
Pentru a folosi orice cârlig React Router, ar trebui mai întâi să împachetăm aplicația noastră cu BrowserRouter
. Putem apoi imbrica rutele cu Switch
și Route
.
Dar mai întâi, trebuie să instalăm pachetul rulând următoarele comenzi.
npm install react-router-dom
Apoi, trebuie să ne configuram aplicația după cum urmează. Îmi place să fac asta în fișierul meu 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> ); }
Am putea avea cât mai multe Rute posibil în funcție de numărul de componente pe care dorim să le redăm. Aici, am randat doar componenta Employees
. Atributul path
îi spune React Router DOM calea componentei și poate fi evaluat cu un șir de interogări sau diverse alte metode.
Ordinea contează aici. Ruta rădăcină ar trebui să fie plasată sub traseul copil și așa mai departe. Pentru a înlocui această ordine, trebuie să includeți cuvântul cheie exact
pe ruta rădăcină.
<Route path='/' exact > <Employees /> </Route>
Acum că am configurat routerul, putem folosi apoi cârligul useHistory
și alte cârlige React Router în aplicația noastră.
Pentru a utiliza cârligul useHistory
, trebuie mai întâi să îl declarăm după cum urmează.
import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }
Dacă înregistrăm istoricul în consolă, vom vedea mai multe proprietăți asociate cu acesta. Acestea includ block
, createHref
, go
, goBack
, goForward
, length
, listen
, location
, push
, replace
. Deși toate aceste proprietăți sunt utile, cel mai probabil veți folosi history.push
și history.replace
mai des decât alte proprietăți.
Să folosim această proprietate pentru a trece de la o pagină la alta.
Presupunând că dorim să obținem date despre un anumit angajat când facem clic pe numele acestuia. Putem folosi cârligul useHistory
pentru a naviga la noua pagină unde vor fi afișate informațiile angajatului.
function moveToPage = (id) =>{ history.push(`/employees/${id}`) }
Putem implementa acest lucru în fișierul Employee.js
adăugând următoarele.
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> ); }
În funcția pushToPage
, am folosit history
din cârligul useHistory
pentru a naviga la pagina angajatului și a trece ID-ul angajatului alături.
useLocation
Hook
Acest cârlig este livrat și cu React Router DOM. Este un cârlig foarte popular folosit pentru a lucra cu parametrul șir de interogare. Acest cârlig este similar cu window.location
din browser.
import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample
Cârligul useLocation
returnează pathname
, parametrul de search
, hash
și state
. Parametrii cei mai des utilizați includ pathname
și search
, dar ați putea folosi în egală măsură hash
și state
multe în aplicația dvs.
Proprietatea pathname
locației va returna calea pe care am stabilit-o în configurarea Route
. În timp ce search
va returna parametrul de căutare de interogare, dacă există. De exemplu, dacă transmitem 'http://mywebsite.com/employee/?id=1'
la interogarea noastră, pathname
ar fi /employee
, iar search
ar fi ?id=1
.
Apoi putem prelua diferiții parametri de căutare folosind pachete precum șir de interogare sau prin codificarea acestora.
Cârligul useParams
Dacă setăm traseul nostru cu un parametru URL în atributul său cale, putem evalua acești parametri ca perechi cheie/valoare cu ajutorul hook- useParams
.
De exemplu, să presupunem că avem următorul traseu.
<Route path='/employees/:id' > <Employees /> </Route>
Ruta se așteaptă la un id dinamic în locul lui :id
.
Cu ajutorul cârligului useParams
, putem evalua ID-ul transmis de utilizator, dacă există.
De exemplu, presupunând că utilizatorul trece următoarele în funcție de history.push
,
function goToPage = () => { history.push(`/employee/3`) }
Putem folosi cârligul useParams
pentru a accesa acest parametru URL după cum urmează.
import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample
Dacă înregistrăm params
în consolă, vom obține următorul obiect {id: "3"}
.
Cârligul useRouteMatch
Acest cârlig oferă acces la obiectul potrivire. Returnează cea mai apropiată potrivire la o componentă dacă nu i se furnizează niciun argument.
Obiectul de potrivire returnează mai mulți parametri, inclusiv path
(aceeași cu calea specificată în Rută), URL
, obiectul params
și isExact
.
De exemplu, putem folosi useRouteMatch
pentru a returna componente bazate pe traseu.
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;
În codul de mai sus, setăm calea unei rute cu useRouteMatch
și apoi redăm componenta <Employee />
sau <Admin />
în funcție de ruta selectată de utilizator.
Pentru ca acest lucru să funcționeze, mai trebuie să adăugăm ruta în fișierul nostru App.js
... <Route> <CustomRoute /> </Route> ...
Construirea unui cârlig personalizat
Conform documentației React, construirea unui cârlig personalizat ne permite să extragem o logică într-o funcție reutilizabilă. Cu toate acestea, trebuie să vă asigurați că toate regulile care se aplică cârligelor React se aplică cârligului dvs. personalizat. Verificați regulile cârligului React din partea de sus a acestui tutorial și asigurați-vă că cârligul personalizat respectă fiecare dintre ele.
Cârligele personalizate ne permit să scriem funcții o dată și să le reutilizam ori de câte ori sunt necesare și, prin urmare, respectând principiul DRY.
De exemplu, am putea crea un cârlig personalizat pentru a obține poziția de defilare pe pagina noastră, după cum urmează.
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; };
Aici, am definit un cârlig personalizat pentru a determina poziția de defilare pe o pagină. Pentru a realiza acest lucru, am creat mai întâi o stare, scrollPos
, pentru a stoca poziția de defilare. Deoarece aceasta va modifica DOM-ul, trebuie să folosim useLayoutEffect
în loc de useEffect
. Am adăugat un ascultător de evenimente de defilare pentru a captura pozițiile de defilare x și y și apoi am curățat ascultătorul de evenimente. În cele din urmă, ne-am întors la poziția de defilare.
Putem folosi acest cârlig personalizat oriunde în aplicația noastră, apelându-l și folosindu-l așa cum am folosi orice altă stare.
import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App
Aici, am importat cârligul personalizat useScrollPos
pe care l-am creat mai sus. Apoi l-am inițializat și apoi am înregistrat valoarea în consola noastră. Dacă derulăm pe pagină, cârligul ne va arăta poziția derulării la fiecare pas al derulării.
Putem crea cârlige personalizate pentru a face aproape orice ne putem imagina în aplicația noastră. După cum puteți vedea, trebuie pur și simplu să folosim cârligul React încorporat pentru a îndeplini unele funcții. Putem folosi și biblioteci terțe pentru a crea cârlige personalizate, dar dacă facem acest lucru, va trebui să instalăm acea bibliotecă pentru a putea folosi cârlige.
Concluzie
În acest tutorial, ne-am uitat bine la câteva cârlige React utile pe care le veți folosi în majoritatea aplicațiilor dvs. Am examinat ce prezintă acestea și cum să le folosim în aplicația dvs. De asemenea, am analizat câteva exemple de cod pentru a vă ajuta să înțelegeți aceste cârlige și să le aplicați aplicației dvs.
Vă încurajez să încercați aceste cârlige în propria aplicație pentru a le înțelege mai bine.
Resurse de la React Docs
- Întrebări frecvente despre cârlige
- Setul de instrumente Redux
- Utilizarea State Hook
- Utilizarea cârligului de efect
- Referință API Hooks
- React Redux Hooks
- React Router Hooks