Cum să creați un cârlig personalizat de reacție pentru a prelua și stoca datele în cache
Publicat: 2022-03-10componentDidMount()
, dar odată cu introducerea Hooks, puteți construi un hook personalizat care va prelua și va stoca în cache datele pentru dvs. Asta va acoperi acest tutorial.Dacă sunteți începător la React Hooks, puteți începe prin a verifica documentația oficială pentru a înțelege. După aceea, aș recomanda să citești „Noțiuni introductive cu API-ul React Hooks” al lui Shedrack Akintayo. Pentru a vă asigura că urmați, există și un articol scris de Adeneye David Abiodun care acoperă cele mai bune practici cu React Hooks, care sunt sigur că se va dovedi a vă fi util.
Pe parcursul acestui articol, vom folosi API-ul Hacker News Search pentru a construi un cârlig personalizat pe care îl putem folosi pentru a prelua date. Deși acest tutorial va acoperi API-ul Hacker News Search, vom avea cârligul să funcționeze într-un mod în care va returna răspuns de la orice link API valid pe care îl transmitem.
Cele mai bune practici React
React este o bibliotecă JavaScript fantastică pentru construirea de interfețe bogate pentru utilizator. Oferă o abstractizare excelentă a componentelor pentru organizarea interfețelor în cod care funcționează bine și există aproape orice pentru care îl puteți folosi. Citiți un articol similar pe React →
Preluarea datelor într-o componentă React
Înainte de conectarea React, era obișnuit să preluați datele inițiale în metoda ciclului de viață componentDidMount()
și date bazate pe modificarea prop sau a stării în metoda ciclului de viață componentDidUpdate()
.
Iată cum funcționează:
componentDidMount() { const fetchData = async () => { const response = await fetch( `https://hn.algolia.com/api/v1/search?query=JavaScript` ); const data = await response.json(); this.setState({ data }); }; fetchData(); } componentDidUpdate(previousProps, previousState) { if (previousState.query !== this.state.query) { const fetchData = async () => { const response = await fetch( `https://hn.algolia.com/api/v1/search?query=${this.state.query}` ); const data = await response.json(); this.setState({ data }); }; fetchData(); } }
Metoda ciclului de viață componentDidMount
este invocată de îndată ce componenta este montată și, când s-a terminat, ceea ce am făcut a fost să facem o solicitare de căutare a „JavaScript” prin intermediul API-ului Hacker News și să actualizăm starea pe baza răspunsului.
Metoda ciclului de viață componentDidUpdate
, pe de altă parte, este invocată atunci când există o schimbare în componentă. Am comparat interogarea anterioară în stare cu interogarea curentă pentru a preveni invocarea metodei de fiecare dată când setăm „date” în stare. Un lucru pe care îl obținem din utilizarea cârligelor este să combinăm ambele metode de ciclu de viață într-un mod mai curat - ceea ce înseamnă că nu va fi nevoie să avem două metode de ciclu de viață pentru când componenta se montează și când se actualizează.
Preluarea datelor cu useEffect
Hook
Cârligul useEffect
este invocat de îndată ce componenta este montată. Dacă avem nevoie ca hook-ul să fie reluat pe baza unor modificări de prop sau de stare, va trebui să le transmitem matricei de dependențe (care este al doilea argument al hook- useEffect
).
Să explorăm cum să obținem date cu cârlige:
import { useState, useEffect } from 'react'; const [status, setStatus] = useState('idle'); const [query, setQuery] = useState(''); const [data, setData] = useState([]); useEffect(() => { if (!query) return; const fetchData = async () => { setStatus('fetching'); const response = await fetch( `https://hn.algolia.com/api/v1/search?query=${query}` ); const data = await response.json(); setData(data.hits); setStatus('fetched'); }; fetchData(); }, [query]);
În exemplul de mai sus, am transmis query
ca dependență la cârligul nostru useEffect
. Făcând asta, îi spunem lui useEffect
să urmărească modificările interogărilor. Dacă valoarea query
anterioare nu este aceeași cu valoarea curentă, useEffect
este invocat din nou.
Acestea fiind spuse, setăm, de asemenea, mai multe status
pe componentă, după cum este necesar, deoarece acest lucru va transmite mai bine un mesaj pe ecran pe baza unor stări status
. În starea inactivă , am putea informa utilizatorii că ar putea folosi caseta de căutare pentru a începe. În starea de preluare , am putea arăta un filator . Și, în starea preluată , vom reda datele.
Este important să setați datele înainte de a încerca să setați starea la fetched
, astfel încât să puteți preveni o pâlpâire care apare ca urmare a golirii datelor în timp ce setați starea fetched
.
Crearea unui cârlig personalizat
„Un cârlig personalizat este o funcție JavaScript al cărei nume începe cu „utilizare” și care poate apela alte cârlige.”
— React Docs
Asta este cu adevărat și, împreună cu o funcție JavaScript, vă permite să reutilizați o parte de cod în mai multe părți ale aplicației dvs.
Definiția din React Docs l-a dat, dar să vedem cum funcționează în practică cu un cârlig personalizat:
const useCounter = (initialState = 0) => { const [count, setCount] = useState(initialState); const add = () => setCount(count + 1); const subtract = () => setCount(count - 1); return { count, add, subtract }; };
Aici, avem o funcție obișnuită în care luăm un argument opțional, setăm valoarea la starea noastră, precum și adăugăm metodele de add
și subtract
care ar putea fi utilizate pentru a-l actualiza.
Oriunde în aplicația noastră, unde avem nevoie de un contor, putem apela useCounter
ca o funcție obișnuită și putem trece un initialState
, astfel încât să știm de unde să începem numărarea. Când nu avem o stare inițială, implicit la 0.
Iată cum funcționează în practică:
import { useCounter } from './customHookPath'; const { count, add, subtract } = useCounter(100); eventHandler(() => { add(); // or subtract(); });
Ceea ce am făcut aici a fost să importam cârligul nostru personalizat din fișierul în care l-am declarat, astfel încât să îl putem folosi în aplicația noastră. Îi setăm starea inițială la 100, așa că, ori de câte ori apelăm add()
, crește count
cu 1, iar oricând apelăm subtract()
, scade count
cu 1.
Se creează useFetch
Hook
Acum că am învățat cum să creăm un cârlig personalizat simplu, să extragem logica noastră pentru a prelua date într-un cârlig personalizat.
const useFetch = (query) => { const [status, setStatus] = useState('idle'); const [data, setData] = useState([]); useEffect(() => { if (!query) return; const fetchData = async () => { setStatus('fetching'); const response = await fetch( `https://hn.algolia.com/api/v1/search?query=${query}` ); const data = await response.json(); setData(data.hits); setStatus('fetched'); }; fetchData(); }, [query]); return { status, data }; };
Este aproape același lucru pe care l-am făcut mai sus, cu excepția faptului că este o funcție care preia query
și returnează status
și data
. Și acesta este un cârlig useFetch
pe care l-am putea folosi în mai multe componente din aplicația noastră React.
Acest lucru funcționează, dar problema cu această implementare acum este că este specific Hacker News, așa că am putea numi doar useHackerNews
. Ceea ce intenționăm să facem este să creăm un hook useFetch
care poate fi folosit pentru a apela orice URL. Să-l renovăm pentru a include o adresă URL!
const useFetch = (url) => { const [status, setStatus] = useState('idle'); const [data, setData] = useState([]); useEffect(() => { if (!url) return; const fetchData = async () => { setStatus('fetching'); const response = await fetch(url); const data = await response.json(); setData(data); setStatus('fetched'); }; fetchData(); }, [url]); return { status, data }; };
Acum, cârligul nostru useFetch este generic și îl putem folosi așa cum dorim în diferitele noastre componente.
Iată o modalitate de a o consuma:
const [query, setQuery] = useState(''); const url = query && `https://hn.algolia.com/api/v1/search?query=${query}`; const { status, data } = useFetch(url);
În acest caz, dacă valoarea query
este truthy
, continuăm să setăm adresa URL, iar dacă nu este, suntem bine să trecem undefined, deoarece ar fi gestionat în hook. Efectul va încerca să ruleze o dată, indiferent.
Memorarea datelor preluate
Memorarea este o tehnică pe care am folosi-o pentru a ne asigura că nu ajungem la punctul final de hackernews
dacă am solicitat un fel de preluare la o fază inițială. Stocarea rezultatului apelurilor de preluare costisitoare va economisi utilizatorilor ceva timp de încărcare, prin urmare, crește performanța generală.
Notă : pentru mai mult context, puteți consulta explicația Wikipedia despre Memoization.
Să vedem cum am putea face asta!
const cache = {}; const useFetch = (url) => { const [status, setStatus] = useState('idle'); const [data, setData] = useState([]); useEffect(() => { if (!url) return; const fetchData = async () => { setStatus('fetching'); if (cache[url]) { const data = cache[url]; setData(data); setStatus('fetched'); } else { const response = await fetch(url); const data = await response.json(); cache[url] = data; // set response in cache; setData(data); setStatus('fetched'); } }; fetchData(); }, [url]); return { status, data }; };
Aici mapăm adresele URL cu datele lor. Deci, dacă facem o solicitare de preluare a unor date existente, setăm datele din cache-ul nostru local, altfel, facem cererea și setăm rezultatul în cache. Acest lucru ne asigură că nu efectuăm un apel API atunci când avem datele disponibile la nivel local. Vom observa, de asemenea, că eliminăm efectul dacă adresa URL este falsy
, astfel încât să ne asigurăm că nu procedăm la preluarea datelor care nu există. Nu o putem face înainte de useEffect
hook, deoarece aceasta va fi împotriva uneia dintre regulile hook-urilor, care este să apelăm întotdeauna hook-uri la nivelul superior.
Declararea cache
-ului într-un domeniu diferit funcționează, dar face ca hook-ul nostru să fie împotriva principiului unei funcții pure. În plus, vrem să ne asigurăm că React ne ajută să ne curățăm mizeria atunci când nu mai dorim să folosim componenta. Vom explora useRef
pentru a ne ajuta să reușim acest lucru.
Memorarea datelor cu useRef
„useRef
este ca o casetă care poate deține o valoare mutabilă în.current property
.”
— React Docs
Cu useRef
, putem seta și prelua cu ușurință valorile modificabile, iar valoarea acesteia persistă pe tot parcursul ciclului de viață al componentei.
Să înlocuim implementarea noastră cache cu ceva magie useRef
!
const useFetch = (url) => { const cache = useRef({}); const [status, setStatus] = useState('idle'); const [data, setData] = useState([]); useEffect(() => { if (!url) return; const fetchData = async () => { setStatus('fetching'); if (cache.current[url]) { const data = cache.current[url]; setData(data); setStatus('fetched'); } else { const response = await fetch(url); const data = await response.json(); cache.current[url] = data; // set response in cache; setData(data); setStatus('fetched'); } }; fetchData(); }, [url]); return { status, data }; };
Aici, cache-ul nostru se află acum în cârligul nostru useFetch
cu un obiect gol ca valoare inițială.
Încheierea
Ei bine, am afirmat că setarea datelor înainte de a seta starea preluată a fost o idee bună, dar există două probleme potențiale pe care le-am putea avea și cu asta:
- Testul nostru unitar ar putea eșua, deoarece matricea de date nu este goală în timp ce suntem în starea de preluare. React ar putea de fapt să modifice starea loturilor, dar nu poate face asta dacă este declanșat asincron;
- Aplicația noastră redă mai mult decât ar trebui.
Să facem o curățare finală a cârligului nostru useFetch
.,Vom începe prin a trece useState
-urile noastre la un useReducer
. Să vedem cum funcționează!
const initialState = { status: 'idle', error: null, data: [], }; const [state, dispatch] = useReducer((state, action) => { switch (action.type) { case 'FETCHING': return { ...initialState, status: 'fetching' }; case 'FETCHED': return { ...initialState, status: 'fetched', data: action.payload }; case 'FETCH_ERROR': return { ...initialState, status: 'error', error: action.payload }; default: return state; } }, initialState);
Aici, am adăugat o stare inițială care este valoarea inițială pe care am transmis-o fiecăruia dintre useState
-urile noastre individuale. În useReducer
-ul nostru, verificăm ce tip de acțiune dorim să realizăm și setăm valorile adecvate pe baza acesteia.
Acest lucru rezolvă cele două probleme pe care le-am discutat mai devreme, deoarece acum putem seta starea și datele în același timp pentru a ajuta la prevenirea stărilor imposibile și a redărilor inutile.
Mai rămâne doar un lucru: curățarea efectului nostru secundar. Fetch implementează API-ul Promise, în sensul că ar putea fi rezolvat sau respins. Dacă hook-ul nostru încearcă să facă o actualizare în timp ce componenta s-a demontat din cauza unei Promise
care tocmai a fost rezolvată, React va returna Can't perform a React state update on an unmounted component.
Să vedem cum putem remedia asta cu useEffect
clean-up!
useEffect(() => { let cancelRequest = false; if (!url) return; const fetchData = async () => { dispatch({ type: 'FETCHING' }); if (cache.current[url]) { const data = cache.current[url]; dispatch({ type: 'FETCHED', payload: data }); } else { try { const response = await fetch(url); const data = await response.json(); cache.current[url] = data; if (cancelRequest) return; dispatch({ type: 'FETCHED', payload: data }); } catch (error) { if (cancelRequest) return; dispatch({ type: 'FETCH_ERROR', payload: error.message }); } } }; fetchData(); return function cleanup() { cancelRequest = true; }; }, [url]);
Aici, setăm cancelRequest
la true
după ce l-am definit în interiorul efectului. Deci, înainte de a încerca să facem modificări de stare, mai întâi confirmăm dacă componenta a fost demontată. Dacă a fost demontat, omitem actualizarea stării și dacă nu a fost demontat, actualizăm starea. Acest lucru va rezolva eroarea de actualizare a stării React și, de asemenea, va preveni condițiile de cursă în componentele noastre.
Concluzie
Am explorat câteva concepte de cârlige pentru a ajuta la preluarea și stocarea în cache a datelor din componentele noastre. De asemenea, am curățat cârligul useEffect
, care ajută la prevenirea unui număr mare de probleme în aplicația noastră.
Dacă aveți întrebări, nu ezitați să le trimiteți în secțiunea de comentarii de mai jos!
- Vezi repo pentru acest articol →
Referințe
- „Prezentarea cârligelor”, React Docs
- „Noțiuni introductive cu API-ul React Hooks”, Shedrack Akintayo
- „Cele mai bune practici cu React Hooks”, Adeneye David Abiodun
- „Programare funcțională: funcții pure”, Arne Brasseur