Võimendades vananenud-pikendama HTTP Cache-Control
pikendamine on populaarne tehnika. See hõlmab vahemälus olevate (aegunud) varade kasutamist, kui need on vahemälus, ja seejärel vahemälu pikendamist ja vajaduse korral selle värskendamist vara uuema versiooniga. Sellest ka nimi stale-while-revalidate
.
stale-while-revalidate
TöötabKui taotlus saadetakse esimest korda, salvestab see brauser vahemällu. Seejärel, kui sama taotlus saadetakse teist korda, kontrollitakse kõigepealt vahemälu. Kui selle päringu vahemälu on saadaval ja kehtiv, tagastatakse vahemälu vastusena. Seejärel kontrollitakse vahemälu püsivust ja värskendatakse, kui see on aegunud. The vahemälu püsivus määratakse max-age
väärtus Cache-Control
päis koos stale-while-revalidate
.
See võimaldab kiire lehe laadimine , kuna vahemällu salvestatud varad pole enam kriitilisel teel. Need laaditakse koheselt. Kuna arendajad kontrollivad vahemälu kasutamise ja värskendamise sagedust, võivad nad takistada brausereid kasutajatele liiga aegunud andmete kuvamisel.
Lugejad võivad mõelda, et kui neil on võimalik lasta serveril oma vastustes teatud päiseid kasutada ja lasta brauseril need sealt võtta, siis mis on selle kasutamise vajadus Reageeri ja konksud vahemällu salvestamiseks?
Selgub, et serveri ja brauseri lähenemine töötab hästi ainult siis, kui soovime staatilist sisu vahemällu salvestada. Kuidas on lood stale-while-revalidate
dünaamilise API jaoks? max-age
Jaoks on raske leida häid väärtusi ja stale-while-revalidate
sellisel juhul. Sageli on parim valik vahemälu kehtetuks muutmine ja värske vastuse toomine iga kord, kui päring saadetakse. See tähendab tegelikult seda, et vahemällu ei jää üldse. Aga koos Reactiga ja Konksud , saame paremini hakkama.
stale-while-revalidate
API jaoksMärkasime, et HTTP-d stale-while-revalidate
ei tööta hästi dünaamiliste taotlustega nagu API-kõned.
Isegi kui me seda lõpuks kasutame, tagastab brauser kas vahemälu või värske vastuse, mitte mõlemad. See ei sobi API taotlusega, kuna soovime värskeid vastuseid iga kord, kui päring saadetakse. Värskete vastuste ootamine aga viib rakenduse sisuka kasutatavuse edasi.
Mida me siis teeme?
Rakendame kohandatud vahemälumehhanismi. Selle raames mõtleme välja, kuidas nii vahemälu kui ka värske vastus tagastada. UI-s asendatakse vahemällu salvestatud vastus uue vastusega, kui see on saadaval. Nii näeks välja loogika:
See lähenemine võimaldab koheseid kasutajaliidese värskendusi - kuna iga API taotlus on vahemällu salvestatud -, aga ka kasutajaliidese õigsust, kuna värskeid vastuseandmeid kuvatakse kohe, kui need on saadaval.
Selles õpetuses näeme järkjärgulist lähenemisviisi selle rakendamiseks. Me nimetame seda lähenemist seisma jäänud-värskendama kuna kasutajaliides on tegelikult värskendatud kui see saab värske vastuse.
Selle õpetuse käivitamiseks vajame kõigepealt API-d, kust andmeid toome. Õnneks on saadaval palju proovitud API-teenuseid. Selle õpetuse jaoks kasutame reqres.in .
Meie toodud andmed on page
-ga kasutajate loend päringu parameeter. Koodide toomine näeb välja järgmine:
fetch('https://reqres.in/api/users?page=2') .then(res => res.json()) .then(json => { console.log(json); });
Selle koodi käivitamine annab meile järgmise väljundi. Siin on selle kordumatu versioon:
{ page: 2, per_page: 6, total: 12, total_pages: 2, data: [ { id: 7, email: ' [email protected] ', first_name: 'Michael', last_name: 'Lawson', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/follettkyle/128.jpg' }, // 5 more items ] }
Näete, et see on nagu päris API. Meil on vastuses paginatsioon. page
päringu parameeter vastutab lehe muutmise eest ja meil on andmekogumis kokku kaks lehte.
Vaatame, kuidas kasutame API-d rakenduses React. Kui teame, kuidas seda teha, selgitame välja vahemäluosa. Kasutame oma komponendi loomiseks klassi. Siin on kood:
import React from 'react'; import PropTypes from 'prop-types'; export default class Component extends React.Component { state = { users: [] }; componentDidMount() { this.load(); } load() { fetch(`https://reqres.in/api/users?page=${this.props.page}`) .then(res => res.json()) .then(json => { this.setState({ users: json.data }); }); } componentDidUpdate(prevProps) { if (prevProps.page !== this.props.page) { this.load(); } } render() { const users = this.state.users.map(user => ( {user.first_name} {user.last_name}
)); return {users} ; } } Component.propTypes = { page: PropTypes.number.isRequired };
Pange tähele, et saame page
väärtus props
kaudu, nagu see juhtub sageli reaalsetes rakendustes. Samuti on meil componentDidUpdate
funktsioon, mis laadib iga kord juurde API andmed this.props.page
muudatused.
Siinkohal näitab see kuue kasutaja loendit, kuna API tagastab lehel kuus üksust:
Kui soovime sellele lisada aegunud värskendamise vahemälu, peame oma rakenduse loogika värskendama järgmiselt:
Saame seda teha globaalse CACHE
objekt, mis salvestab vahemälu ainulaadselt. Unikaalsuse huvides võime kasutada this.props.page
väärtus võtmena meie CACHE
objekt. Seejärel kodeerime lihtsalt ülalnimetatud algoritmi.
import apiFetch from './apiFetch'; const CACHE = {}; export default class Component extends React.Component { state = { users: [] }; componentDidMount() { this.load(); } load() { if (CACHE[this.props.page] !== undefined) { this.setState({ users: CACHE[this.props.page] }); } apiFetch(`https://reqres.in/api/users?page=${this.props.page}`).then( json => { CACHE[this.props.page] = json.data; this.setState({ users: json.data }); } ); } componentDidUpdate(prevProps) { if (prevProps.page !== this.props.page) { this.load(); } } render() { // same render code as above } }
Kuna vahemälu tagastatakse kohe pärast selle leidmist ja kuna uued vastuseandmed tagastab setState
samuti tähendab see, et meil on sujuvaid kasutajaliidese värskendusi ja rakenduses pole enam ooteaega alates teisest taotlusest. See on täiuslik ja see on lühidalt värskendatud värskendamise meetod.
apiFetch
funktsioon pole siin midagi muud kui ümbris fetch
et saaksime vahemällu salvestamise eeliseid reaalajas näha. Ta teeb seda, lisades juhusliku kasutaja users
loendisse tagastab API taotlus. Samuti lisab see juhusliku viivituse:
export default async function apiFetch(...args) { await delay(Math.ceil(400 + Math.random() * 300)); const res = await fetch(...args); const json = await res.json(); json.data.push(getFakeUser()); return json; } function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
getFakeUser()
Funktsioon siin vastutab võltsitud kasutajaobjekti loomise eest.
Nende muudatustega on meie API varasemast reaalsem.
Arvestades seda, kui muudame page
rekvisiit edastati Component
meie põhikomponendilt näeme API vahemälu toimimist. Proovige klõpsata nuppu Lülita üks kord iga sekundi järel see CodeSandbox ja peaksite nägema sellist käitumist:
Kui vaatate tähelepanelikult, juhtub paar asja.
See on see, aegunud värskendamise vahemälu, mida me otsisime. Kuid see lähenemine kannatab koodi dubleerimise probleemi all. Vaatame, kuidas läheb, kui meil on veel üks vahemäluga andmete hankimise komponent. See komponent näitab üksusi erinevalt meie esimesest komponendist.
Saame seda teha, kopeerides loogika lihtsalt esimesest komponendist. Meie teine komponent näitab kasside loendit:
const CACHE = {}; export default class Component2 extends React.Component { state = { cats: [] }; componentDidMount() { this.load(); } load() { if (CACHE[this.props.page] !== undefined) { this.setState({ cats: CACHE[this.props.page] }); } apiFetch(`https://reqres.in/api/cats?page=${this.props.page}`).then( json => { CACHE[this.props.page] = json.data; this.setState({ cats: json.data }); } ); } componentDidUpdate(prevProps) { if (prevProps.page !== this.props.page) { this.load(); } } render() { const cats = this.state.cats.map(cat => ( {cat.name} (born {cat.year})
)); return {cats} ; } }
Nagu näete, on siin kasutatav komponentloogika üsna sama mis esimene komponent. Ainus erinevus on taotletavas lõpp-punktis ja selles, et see näitab loendiüksusi erinevalt.
Nüüd näitame mõlemat komponenti kõrvuti. Sa näed nad käituvad sarnaselt :
Selle tulemuse saavutamiseks pidime tegema palju koodide dubleerimist. Kui meil oleks mitu sellist komponenti, dubleeriksime liiga palju koodi.
Selle dubleerimata lahendamiseks võib meil olla andmete toomiseks ja vahemällu salvestamiseks ning rekvisiitidena edastamiseks kõrgema järgu komponent. See pole ideaalne, kuid töötab. Aga kui me peaksime tegema mitu taotlust ühes komponendis, muutuks mitme kõrgema järgu komponendi omamine väga kiiresti kole.
Seejärel on meil renderdamise rekvisiitide muster, mis on ilmselt parim viis seda klassi komponentides teha. See töötab suurepäraselt, kuid jällegi on see altid „põrgu mähkima“ ja nõuab kohati praeguse konteksti sidumist. See pole suurepärane arendaja kogemus ja võib põhjustada pettumust ja vigu.
See on koht, kus React Hooks päästab päeva. Need võimaldavad meil komponendiloogika kastida korduvkasutatavasse konteinerisse, et saaksime seda kasutada mitmes kohas. Reageeri konksud võeti kasutusele reaktsioonis 16.8 ja need töötavad ainult funktsioonikomponentidega. Enne kui jõuame React vahemälu juhtimise juurde - eriti sisu vahemällu salvestamise konksudega -, vaatame kõigepealt, kuidas me funktsiooni komponentides lihtsalt andmeid toome.
Funktsioonikomponentide API-andmete toomiseks kasutame useState
ja useEffect
konksud.
useState
on analoogne klassi komponentidega ’state
ja setState
. Me kasutame seda konksu, et funktsionaalse komponendi sees oleks aatomikonteinerid.
useEffect
on elutsükli konks ja võite mõelda sellest kui kombinatsioon componentDidMount
, componentDidUpdate
ja componentWillUnmount
. Teine parameeter edastati useEffect
nimetatakse sõltuvuse massiiviks. Kui sõltuvusmassiiv muutub, edastati tagasihelistamine esimese argumendina useEffect
-le käivitatakse uuesti.
Andmehankimise rakendamiseks kasutame neid konksusid järgmiselt.
import React, { useState, useEffect } from 'react'; export default function Component({ page }) { const [users, setUsers] = useState([]); useEffect(() => { fetch(`https://reqres.in/api/users?page=${page}`) .then(res => res.json()) .then(json => { setUsers(json.data); }); }, [page]); const usersDOM = users.map(user => ( {user.first_name} {user.last_name}
)); return {usersDOM} ; }
Täpsustades page
kui sõltuvust useEffect
-st, anname Reactile juhise käivitada meie useEffecti tagasihelistamine iga kord page
on muudetud. See on täpselt nagu componentDidUpdate
. Samuti useEffect
töötab alati esimest korda, nii et see töötab nagu componentDidMount
ka.
Me teame, et useEffect
on sarnane komponentide olelusringi meetoditega. Nii saame muuta talle edastatud tagasihelistamisfunktsiooni, et luua klassikomponentides olnud aegunud värskendamise vahemälu. Kõik jääb samaks, välja arvatud useEffect
konks.
const CACHE = {}; export default function Component({ page }) { const [users, setUsers] = useState([]); useEffect(() => { if (CACHE[page] !== undefined) { setUsers(CACHE[page]); } apiFetch(`https://reqres.in/api/users?page=${page}`).then(json => { CACHE[page] = json.data; setUsers(json.data); }); }, [page]); // ... create usersDOM from users return {usersDOM} ; }
Seega on meil funktsiooni komponendis töötav aegunud värskendamise vahemälu.
Saame sama teha ka teise komponendi puhul, see tähendab, et teisendada see toimima ja rakendada vahemälu värskendamise vahemälu. Tulemus on identne sellega, mis meil klassides oli.
Kuid see pole parem kui klassi komponendid? Nii et vaatame, kuidas saame kohandatud konksu jõudu kasutada moodulite aegunud-värske-värskenduse loogika loomiseks, mida saame kasutada mitmes komponendis.
Kõigepealt kitsendame loogikat, mille soovime kohandatud konksu sisse viia. Eelmist koodi vaadates teate, et see on useState
ja useEffect
osa. Täpsemalt öeldes on see loogika, mida me tahame moduleerida.
const [users, setUsers] = useState([]); useEffect(() => { if (CACHE[page] !== undefined) { setUsers(CACHE[page]); } apiFetch(`https://reqres.in/api/users?page=${page}`).then(json => { CACHE[page] = json.data; setUsers(json.data); }); }, [page]);
Kuna peame selle muutma üldiseks, peame URL-i muutma dünaamiliseks. Nii et meil peab olema url
argumendina. Peame värskendama ka vahemäluloogikat, kuna mitmel taotlusel võib olla sama page
väärtus. Õnneks, kui page
on lisatud lõpp-URL-i, annab see iga kordumatu päringu jaoks ainulaadse väärtuse. Seega saame vahemällu salvestamise võtmena kasutada kogu URL-i:
const [data, setData] = useState([]); useEffect(() => { if (CACHE[url] !== undefined) { setData(CACHE[url]); } apiFetch(url).then(json => { CACHE[url] = json.data; setData(json.data); }); }, [url]);
See on peaaegu kõik. Pärast funktsiooni sisse pakkimist on meil oma konks. Vaadake allpool.
const CACHE = {}; export default function useStaleRefresh(url, defaultValue = []) { const [data, setData] = useState(defaultValue); useEffect(() => { // cacheID is how a cache is identified against a unique request const cacheID = url; // look in cache and set response if present if (CACHE[cacheID] !== undefined) { setData(CACHE[cacheID]); } // fetch new data apiFetch(url).then(newData => { CACHE[cacheID] = newData.data; setData(newData.data); }); }, [url]); return data; }
Pange tähele, et oleme lisanud veel ühe argumendi nimega defaultValue
selle juurde. API-kõne vaikeväärtus võib olla erinev, kui kasutate seda konksu mitmes komponendis. Sellepärast oleme selle kohandanud.
Sama saab teha ka data
sisestage newData
objekt. Kui teie kohandatud konks tagastab mitmesuguseid andmeid, võiksite lihtsalt naasta newData
ja mitte newData.data
ja käsitsege seda läbimist komponendi poolel.
Nüüd, kui meil on oma kohandatud konks, mis muudab vananenud värskendamise vahemällu rasket tõstmist, ühendame selle oma komponentidega. Pange tähele, kui palju koodi suutsime vähendada. Kogu meie komponent on nüüd vaid kolm väidet. See on suur võit.
import useStaleRefresh from './useStaleRefresh'; export default function Component({ page }) { const users = useStaleRefresh(`https://reqres.in/api/users?page=${page}`, []); const usersDOM = users.map(user => ( {user.first_name} {user.last_name}
)); return {usersDOM} ; }
Sama saame teha ka teise komponendi puhul. See näeb välja selline:
export default function Component2({ page }) { const cats = useStaleRefresh(`https://reqres.in/api/cats?page=${page}`, []); // ... create catsDOM from cats return {catsDOM} ; }
Selle konksu kasutamisel on lihtne mõista, kui palju katlakoodi koodi saame kokku hoida. Ka kood näeb parem välja. Kui soovite, et kogu rakendus toimiks, minge üle aadressile see CodeSandbox .
useStaleRefresh
Nüüd, kui meil on põhitõed olemas, saame oma kohandatud konksule lisada rohkem funktsioone. Näiteks võime lisada isLoading
konksu väärtus, mis on tõene alati, kui saadetakse kordumatu taotlus ja meil pole vahepeal vahemälu näidata.
Teeme seda, kui meil on isLoading
jaoks eraldi olek ja selle seadistamine konksu oleku järgi. See tähendab, et kui vahemällu salvestatud veebisisu pole saadaval, määrame selle väärtuseks true
, vastasel juhul määrame selle false
.
Siin on uuendatud konks:
export default function useStaleRefresh(url, defaultValue = []) { const [data, setData] = useState(defaultValue); const [isLoading, setLoading] = useState(true); useEffect(() => { // cacheID is how a cache is identified against a unique request const cacheID = url; // look in cache and set response if present if (CACHE[cacheID] !== undefined) { setData(CACHE[cacheID]); setLoading(false); } else { // else make sure loading set to true setLoading(true); } // fetch new data apiFetch(url).then(newData => { CACHE[cacheID] = newData.data; setData(newData.data); setLoading(false); }); }, [url]); return [data, isLoading]; }
Nüüd saame uut isLoading
kasutada meie komponentide väärtus.
export default function Component({ page }) { const [users, isLoading] = useStaleRefresh( `https://reqres.in/api/users?page=${page}`, [] ); if (isLoading) { return Loading ; } // ... create usersDOM from users return {usersDOM} ; }
Märka seda sellega tehtud , näete teksti „Laadimine”, kui unikaalne taotlus saadetakse esmakordselt ja vahemälu pole olemas.
useStaleRefresh
Toetage kõiki async
FunktsioonSaame muuta oma kohandatud konksu veelgi võimsamaks, muutes selle mis tahes async
funktsioon, mitte ainult GET
võrgutaotlused. Selle põhiidee jääb samaks.
function.name
Lihtne liitmine ja arguments
töötab meie kasutuse puhul vahemälu võtmena. Selle abil näeb meie konks välja selline:
import { useState, useEffect, useRef } from 'react'; import isEqual from 'lodash/isEqual'; const CACHE = {}; export default function useStaleRefresh(fn, args, defaultValue = []) { const prevArgs = useRef(null); const [data, setData] = useState(defaultValue); const [isLoading, setLoading] = useState(true); useEffect(() => { // args is an object so deep compare to rule out false changes if (isEqual(args, prevArgs.current)) { return; } // cacheID is how a cache is identified against a unique request const cacheID = hashArgs(fn.name, ...args); // look in cache and set response if present if (CACHE[cacheID] !== undefined) { setData(CACHE[cacheID]); setLoading(false); } else { // else make sure loading set to true setLoading(true); } // fetch new data fn(...args).then(newData => { CACHE[cacheID] = newData; setData(newData); setLoading(false); }); }, [args, fn]); useEffect(() => { prevArgs.current = args; }); return [data, isLoading]; } function hashArgs(...args) { return args.reduce((acc, arg) => stringify(arg) + ':' + acc, ''); } function stringify(val) { return typeof val === 'object' ? JSON.stringify(val) : String(val); }
Nagu näete, kasutame funktsiooni nimetuse ja selle kitsendatud argumentide kombinatsiooni, et funktsiooni kõne ainulaadselt tuvastada ja seeläbi vahemällu salvestada. See töötab meie lihtsa rakenduse puhul, kuid see algoritm on altid kokkupõrgetele ja aeglastele võrdlustele. (Sersialiseerimatute argumentide korral ei tööta see üldse.) Nii et reaalsetes rakendustes on sobivam korralik räsimisalgoritm.
Teine asi, mida siin tähele panna, on useRef
. useRef
kasutatakse andmete säilitamiseks kogu ümbritseva komponendi elutsükli jooksul. Kuna args
on massiiv - mis on JavaScripti objekt - põhjustab iga komponendi konksu abil uuesti renderdamine args
võrdluskurss muutmiseks. Kuid args
on osa meie esimesest useEffect
sõltuvuste loendist. Niisiis args
muutmine võib muuta meie useEffect
joosta ka siis, kui midagi ei muutunud. Selle vastu võitlemiseks võrdleme põhjalikult vana ja praegust args
kasutades isEqual ja laske ainult useEffect
tagasihelistamine töötab, kui args
tegelikult muutunud.
Nüüd saame seda uut useStaleRefresh
kasutada konks järgmiselt. Pange tähele muutust defaultValue
siin. Kuna see on üldotstarbeline konks, ei looda data
tagastamisel oma konks sisestage vastuse objekt.
export default function Component({ page }) { const [users, isLoading] = useStaleRefresh( apiFetch, [`https://reqres.in/api/users?page=${page}`], { data: [] } ); if (isLoading) { return Loading ; } const usersDOM = users.data.map(user => ( {user.first_name} {user.last_name}
)); return {usersDOM} ; }
Kogu koodi leiate lehelt see CodeSandbox .
useStaleRefresh
Selles artiklis loodud konks on tõestus kontseptsioonist, mis näitab, mis on võimalik React Hooksiga. Proovige koodiga mängida ja vaadake, kas saate selle oma rakendusse sobitada.
Teise võimalusena võite proovida ka vananenud värskendamist populaarse, hästi hooldatud avatud lähtekoodiga teegi kaudu swr või reageeri-päringu . Mõlemad on võimsad raamatukogud ja toetavad paljusid funktsioone, mis aitavad API taotlustel.
React Hooks on mängude vahetaja. Need võimaldavad meil komponendiloogikat elegantselt jagada. Varem polnud see võimalik, sest komponendi olek, olelusringi meetodid ja renderdamine olid kõik pakitud ühte olemisse: klassi komponendid. Nüüd on meil kõigi nende jaoks erinevad moodulid. See sobib suurepäraselt komponeeritavuse ja parema koodi kirjutamise jaoks. Kasutan funktsioonide komponente ja konksusid kõigi uute kirjutatavate Reacti koodide jaoks ja soovitan seda tungivalt kõigile Reacti arendajatele.
Vananenud vahemälu on vahemälu, mis ei sobi kasutamiseks, kuna see sisaldab aegunud andmeid. HTTP kontekstis juhtub see siis, kui vahemälu max-age või s-maxage on aegunud. See sarnaneb sellega, kuidas pikka aega hoitud toit aegub, sellest tuleneb mõiste „aegunud vahemälu”.
Vananenud sisu on sisu, mille kehtivus on aegunud. CDN-ide kontekstis tähendab see, et sisu on eksisteerinud rohkem kui sellele lisatud TTL-väärtus. Nii et see ei sobi kasutamiseks, sellest tuleneb mõiste „aegunud”.
React Hook on funktsioon, mis võimaldab meil ühendada React Function Component oleku ja olelusringi meetoditega. Seetõttu lubab konks meil klassi komponentide kasutamise vahele jätta, kuna selle kaudu on juurdepääsetavad elutsükli meetodid, näiteks
Cache-Control on HTTP päis, mis määrab päringuga seotud vahemälu. Seda saab määratleda iseseisvalt nii päringu- kui ka vastuse päises. Vahemälukontrolli abil tuleb määratleda direktiivid nagu vahemälu puudumine, uuesti kinnitamise kohustus ja maksimaalne vanus, et määrata, kuidas päringu vastust vahemällu tuleb salvestada.
HTTP-i vahemälu kontrollimise must-revalidate-direktiiv määrab, et kui vahemälu on aegunud, ei tohi seda kasutada enne edasist kinnitamist. Kehtivuse pikendamine toimub, kui võtate ühendust vahemälu päritoluserveriga ja kontrollige, kas uuem versioon on olemas. Kui on uuem versioon, siis see tõmmatakse ja seejärel kasutatakse.
Vahemällu salvestatud andmed pole rangelt olulised, kuid on soovitavad, kuna parandavad tõhusat toimivust. Süsteem suudab vahemällu salvestatud andmeid koheselt serveerida, samas kui värskete andmete laadimine võtab aega. Vahemällu salvestatud andmed hõivavad kettaruumi, seega on hea need vajadusel kustutada.