useDeferredValue
useDeferredValue
je React Hook koji vam omogućava da odložite ažuriranje dela UI-a.
const deferredValue = useDeferredValue(value)
Reference
useDeferredValue(value, initialValue?)
Pozovite useDeferredValue
na vrhu vaše komponente da dobijete odloženu verziju te vrednosti.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
Parametri
value
: Vrednost koju želite da odložite. Može imati bilo koji tip.- opcioni
initialValue
: Vrednost koja se koristi prilikom inicijalnog rendera komponente. Ako se izostavi,useDeferredValue
neće odložiti tokom inicijalnog rendera, jer ne postoji prethodna vrednostvalue
-a koja može biti renderovana.
Povratne vrednosti
currentValue
: Tokom inicijalnog rendera, povratna odložena vrednost će bitiinitialValue
ili ista kao prosleđena vrednost. Tokom ažuriranja, React će prvo pokušati ponovno renderovanje sa starom vrednošću (znači da će vratiti staru vrednost), a onda pokušati drugi ponovni render u pozadini sa novom vrednošću (znači da će vratiti ažuriranu vrednost).
Upozorenja
-
Kada je ažuriranje unutar Transition-a,
useDeferredValue
uvek vraća novivalue
i ne stvara odloženi render, pošto je ažuriranje već odloženo. -
Vrednosti koje prosleđujete u
useDeferredValue
bi trebale biti ili primitivne (poput stringova i brojeva) ili objekti kreirani izvan renderovanja. Ako kreirate novi objekat tokom renderovanja i odmah ga prosledite uuseDeferredValue
, biće drugačiji u svakom renderu, stvarajući nepotrebne ponovne rendere u pozadini. -
Kada
useDeferredValue
primi drugačiju vrednost (upoređenu saObject.is
), kao dodatak na trenutni render (koji još uvek koristi prethodnu vrednost), zakazaće se ponovni render u pozadini sa novom vrednošću. Ponovni render u pozadini može biti prekinut: ako postoji drugo ažuriranjevalue
-a, React će restartovati ponovni render u pozadini. Na primer, ako korisnik brže kuca u input nego što se tabela koja prima odloženu vrednost može ponovo renderovati, tabela će se ponovo renderovati tek kada korisnik prestane sa kucanjem. -
useDeferredValue
je integrisan sa<Suspense>
-om. Ako pozadinsko ažuriranje prouzrokovano novom vrednošću suspenduje UI, korisnik neće videti fallback. Videće samo staru odloženu vrednost dok se podaci ne učitaju. -
useDeferredValue
sam od sebe ne sprečava dodatne mrežne zahteve. -
Ne postoji fiksno kašnjenje koje prouzrokuje
useDeferredValue
. Čim React završi originalni ponovni render, odmah će početi da radi na ponovnom renderu u pozadini sa novom odloženom vrednošću. Bilo koja ažuriranja prouzrokovana event-ovima (poput pisanja) će dobiti prioritet i prekinuti ponovni render u pozadini. -
Ponovni render u pozadini prouzrokovan
useDeferredValue
-om neće okinuti Effect-e dok ne bude završen. Ako se ponovni render u pozadini suspenduje, Effect-i će se pokrenuti nakon što se podaci učitaju i UI ažurira.
Upotreba
Prikazivanje zastarelog sadržaja dok se novi sadržaj učitava
Pozovite useDeferredValue
na vrhu vaše komponente da odložite ažuriranje nekog dela UI-a.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
Tokom inicijalnog rendera, odložena vrednost će biti ista kao i vrednost koju ste prosledili.
Tokom ažuriranja, odložena vrednost će “lag-ovati” za najnovijom vrednošću. Konkretno, React će prvo ponovo renderovati bez ažuriranja odložene vrednosti, a onda pokušati da ponovo renderuje u pozadini sa novodobijenom vrednošću.
Prođimo kroz primer da vidite kada je ovo korisno.
U ovom primeru, SearchResults
komponenta se suspenduje dok fetch-uje rezultate pretrage. Probajte ukucati "a"
, sačekati rezultate, a onda promeniti na "ab"
. Rezultati za "a"
će biti zamenjeni sa fallback-om za učitavanje.
import { Suspense, useState } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); return ( <> <label> Pretraži albume: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Učitavanje...</h2>}> <SearchResults query={query} /> </Suspense> </> ); }
Uobičajeni alternativni UI šablon je odlaganje ažuriranja liste rezultata i prikazivanje prethodnih rezultata dok novi rezultati ne budu spremni. Pozovite useDeferredValue
da prosledite odloženu vrednost query-ja:
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Pretraži albume:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Učitavanje...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
query
će se odmah ažurirati, pa će input prikazati novu vrednost. Međutim, deferredQuery
će zadržati njegovu prethodnu vrednost dok se podaci ne učitaju, pa će SearchResults
na trenutak prikazivati zastarele rezultate.
Unestite "a"
u primer ispod, sačekajte da se rezultati učitaju, a onda promenite na "ab"
. Primetite kako sad, umesto Suspense fallback-a, vidite zastarelu listu rezultata dok se novi rezultati ne učitaju:
import { Suspense, useState, useDeferredValue } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); return ( <> <label> Pretraži albume: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Učitavanje...</h2>}> <SearchResults query={deferredQuery} /> </Suspense> </> ); }
Deep Dive
Možete zamisliti da se to dešava u dva koraka:
-
Prvo, React ponovo renderuje sa novom
query
vrednošću ("ab"
), ali sa staromdeferredQuery
vrednošću (još uvek"a"
).deferredQuery
vrednost, koju prosleđujete u listu rezultata, je odložena: “lag-uje za”query
vrednošću. -
U pozadini React pokušava da ponovo renderuje i
query
ideferredQuery
ažuriran na"ab"
. Ako se ovaj ponovni render završi, React će ga prikazati na ekranu. Međutim, ako se suspenduje (rezultati za"ab"
se još nisu učitali), React će odbaciti pokušaj renderovanja i ponovo pokušati ovaj ponovni render kad se podaci učitaju. Korisnik će videti zastarelu odloženu vrednost sve dok podaci ne budu spremni.
Odloženo “pozadinsko” renderovanje može biti prekinuto. Na primer, ako ponovo kucate u input, React će ga odbaciti i restartovati sa novom vrednošću. React će uvek koristiti poslednju dostupnu vrednost.
Primetite da i dalje postoji mrežni zahtev pri svakom pritisku tastera na tastaturi. Ono što je ovde odloženo je prikaz rezultata (dok ne budu spremni), a ne sami mrežni zahtevi. Čak iako korisnik nastavi da kuca, odgovori za svaki taster će biti keširani, pa je pritiskanje Backspace-a trenutno i ne fetch-uje podatke ponovo.
Indikacija da je sadržaj zastareo
U primeru iznad, ništa nije ukazivalo da se lista rezultata za najnoviji upit još uvek učitava. Ovo može biti zbunjujuće za korisnika ako novim rezultatima treba vremena da se učitaju. Da bi korisniku bilo očiglednije da lista rezultata ne odgovara najnovijem upitu, možete dodati vizuelnu indikaciju da je prikazana zastarela lista rezultata:
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>
Sa ovom promenom, čim krenete kucati, zastarela lista rezultata će postati pomalo zamagljena dok se nova lista rezultata ne učita. Takođe možete dodati i CSS transition za postepeno zamagljivanje, kao u primeru ispod:
import { Suspense, useState, useDeferredValue } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; return ( <> <label> Pretraži albume: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Učitavanje...</h2>}> <div style={{ opacity: isStale ? 0.5 : 1, transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear' }}> <SearchResults query={deferredQuery} /> </div> </Suspense> </> ); }
Odlaganje ponovnog rendera za deo UI-a
Takođe, možete primeniti useDeferredValue
kao optimizaciju performansi. Korisno je kada je deo vašeg UI-a spor prilikom ponovnog rendera, ne postoji lak način da ga optimizujete, a želite da ga sprečite da blokira ostatak UI-a.
Zamislite da imate tekstualno polje i komponentu (poput tabele ili dugačke liste) koja se ponovo renderuje pri svakom pritisku tastera na tastaturi:
function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}
Prvo, optimizujte SlowList
da preskoči ponovne rendere kada su joj props-i jednaki. Da biste to uradili, obmotajte je sa memo
:
const SlowList = memo(function SlowList({ text }) {
// ...
});
Međutim, ovo pomaže samo kada su props-i SlowList
-a jednaki kao u prethodnom renderu. Problem koji sada imate je da je sporo kada su props-i drugačiji i kada zapravo trebate prikazati drugačiji vizuelni prikaz.
Konkretno, glavni problem sa performansama je taj da kad god nešto kucate u input, SlowList
prima nove props-e, a ponovno renderovanje celokupnog stabla čini kucanje nezgodnim. U ovom slučaju, useDeferredValue
vam omogućava da date prioritet ažuriranju input-a (koje mora biti brzo) u odnosu na ažuriranje liste rezultata (što je dozvoljeno da bude sporije):
function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}
Ovo ne čini ponovno renderovanje SlowList
-a bržim. Međutim, govori React-u da ponovno renderovanje liste ima manji prioritet, tako da neće blokirati unos sa tastature. Lista će “lag-ovati za” input-om i onda ga “sustići”. Kao i pre, React će pokušati da ažurira listu što je pre moguće, ali neće blokirati korisnika prilikom kucanja.
Primer 1 od 2: Odloženo ponovno renderovanje liste
U ovom primeru, svaka stavka u SlowList
komponenti je veštački usporena kako biste videli da useDeferredValue
omogućava da input ostane responzivan. Kucajte u input i primetite da kucanje deluje brzo, iako lista “lag-uje za” njim.
import { useState, useDeferredValue } from 'react'; import SlowList from './SlowList.js'; export default function App() { const [text, setText] = useState(''); const deferredText = useDeferredValue(text); return ( <> <input value={text} onChange={e => setText(e.target.value)} /> <SlowList text={deferredText} /> </> ); }
Deep Dive
Postoje dve uobičajene tehnike optimizacije koje ste možda ranije koristili u ovakvim slučajevima:
- Debouncing znači da ćete čekati korisnika da prestane sa kucanjem (npr. na sekund) pre ažuriranja liste.
- Throttling znači da ćete ažurirati listu svako malo (npr. najviše jednom u sekundi).
Iako su ove tehnike korisne u nekim slučajevima, useDeferredValue
je bolji za optimizaciju renderovanja jer je duboko integrisan sa samim React-om i prilagođen uređaju korisnika.
Za razliku od debouncing-a ili throttling-a, ne zahteva odabir fiksnog kašnjenja. Ako je uređaj korisnika brz (npr. moćan laptop), odloženi ponovni render se dešava gotovo odmah i neće biti primetan. Ako je uređaj korisnika spor, lista će “lag-ovati za” input-om proporcionalno u odnosu na to koliko je uređaj spor.
Takođe, za razliku od debouncing-a ili throttling-a, odloženi ponovni renderi sa useDeferredValue
se, po default-u, mogu prekinuti. To znači da, ako je React u sred ponovnog renderovanja velike liste, a korisnik pritisne neki taster, React će odbaciti taj ponovni render, obraditi pritisak tastera, i tek onda ponovo započeti renderovanje u pozadini. U poređenju sa tim, debouncing i throttling i dalje pružaju nezgodno iskustvo jer su blokirajući: oni samo odlažu momenat kada renderovanje blokira pritiskanje tastera.
Ako se posao koji optimizujete ne dešava tokom renderovanja, debouncing i throttling su i dalje korisni. Na primer, mogu vam omogućiti da pravite manje mrežnih zahteva. Takođe, ove tehnike možete koristiti zajedno.