Moderne routering aan de clientzijde: de navigatie-API

Het standaardiseren van routering aan de clientzijde via een gloednieuwe API die het bouwen van applicaties met één pagina volledig hervormt.

Browserondersteuning

  • Chroom: 102.
  • Rand: 102.
  • Firefox: niet ondersteund.
  • Safari: niet ondersteund.

Bron

Single-page-applicaties, of SPA's, worden gedefinieerd door een kernfunctie: het dynamisch herschrijven van hun inhoud terwijl de gebruiker interactie heeft met de site, in plaats van de standaardmethode om volledig nieuwe pagina's van de server te laden.

Hoewel SPA's je deze functie hebben kunnen bieden via de History API (of in beperkte gevallen door het #hash-gedeelte van de site aan te passen), is het een onhandige API die is ontwikkeld lang voordat SPA's de norm waren - en het internet schreeuwt om een geheel nieuwe aanpak. De Navigatie-API is een voorgestelde API die deze ruimte volledig op de schop neemt, in plaats van te proberen eenvoudigweg de ruwe kantjes van de History API op te lossen. ( Scroll Restoration heeft bijvoorbeeld de History API gepatcht in plaats van te proberen deze opnieuw uit te vinden.)

Dit bericht beschrijft de navigatie-API op een hoog niveau. Als u het technische voorstel wilt lezen, bekijk dan het conceptrapport in de WICG-repository .

Voorbeeld gebruik

Als u de navigatie-API wilt gebruiken, begint u met het toevoegen van een "navigate" listener aan het globale navigation . Deze gebeurtenis is fundamenteel gecentraliseerd : hij wordt geactiveerd voor alle soorten navigatie, ongeacht of de gebruiker een actie heeft uitgevoerd (zoals op een link klikken, een formulier indienen of heen en weer gaan) of wanneer de navigatie programmatisch wordt geactiveerd (dat wil zeggen via de website van uw site). code). In de meeste gevallen kan uw code het standaardgedrag van de browser voor die actie overschrijven. Voor SPA's betekent dit waarschijnlijk dat de gebruiker op dezelfde pagina moet blijven en dat de inhoud van de site moet worden geladen of gewijzigd.

Er wordt een NavigateEvent doorgegeven aan de "navigate" -listener die informatie over de navigatie bevat, zoals de bestemmings-URL, en waarmee u op één centrale plaats op de navigatie kunt reageren. Een eenvoudige "navigate" -luisteraar zou er als volgt uit kunnen zien:

navigation.addEventListener('navigate', navigateEvent => {
  // Exit early if this navigation shouldn't be intercepted.
  // The properties to look at are discussed later in the article.
  if (shouldNotIntercept(navigateEvent)) return;

  const url = new URL(navigateEvent.destination.url);

  if (url.pathname === '/') {
    navigateEvent.intercept({handler: loadIndexPage});
  } else if (url.pathname === '/cats/') {
    navigateEvent.intercept({handler: loadCatsPage});
  }
});

U kunt op twee manieren met de navigatie omgaan:

  • Aanroepen van intercept({ handler }) (zoals hierboven beschreven) om de navigatie af te handelen.
  • preventDefault() aanroepen, waardoor de navigatie volledig kan worden geannuleerd.

In dit voorbeeld wordt intercept() voor de gebeurtenis aangeroepen. De browser roept de callback van uw handler aan, die de volgende status van uw site zou moeten configureren. Hierdoor wordt een overgangsobject aangemaakt, navigation.transition , dat andere code kan gebruiken om de voortgang van de navigatie bij te houden.

Zowel intercept() als preventDefault() zijn doorgaans toegestaan, maar er zijn gevallen waarin ze niet kunnen worden aangeroepen. U kunt geen navigatie via intercept() afhandelen als de navigatie een cross-origin-navigatie is. En je kunt een navigatie niet annuleren via preventDefault() als de gebruiker op de knoppen Vorige of Volgende in zijn browser drukt; u zou uw gebruikers op uw site niet moeten kunnen vangen. (Dit wordt besproken op GitHub .)

Zelfs als u de navigatie zelf niet kunt stoppen of onderscheppen, wordt de gebeurtenis "navigate" nog steeds geactiveerd. Het is informatief , dus uw code kan bijvoorbeeld een Analytics-gebeurtenis registreren om aan te geven dat een gebruiker uw site verlaat.

Waarom nog een evenement toevoegen aan het platform?

Een "navigate" -gebeurtenislistener centraliseert de verwerking van URL-wijzigingen binnen een SPA. Dit is een lastig voorstel als je oudere API's gebruikt. Als u ooit de routing voor uw eigen SPA heeft geschreven met behulp van de History API, heeft u mogelijk de volgende code toegevoegd:

function updatePage(event) {
  event.preventDefault(); // we're handling this link
  window.history.pushState(null, '', event.target.href);
  // TODO: set up page based on new URL
}
const links = [...document.querySelectorAll('a[href]')];
links.forEach(link => link.addEventListener('click', updatePage));

Dit is prima, maar niet exhaustief. Er kunnen links op uw pagina komen en gaan, en dit is niet de enige manier waarop gebruikers door pagina's kunnen navigeren. Ze kunnen bijvoorbeeld een formulier indienen of zelfs een image map gebruiken. Uw pagina kan hiermee te maken hebben, maar er is een lange reeks mogelijkheden die eenvoudigweg vereenvoudigd kunnen worden, iets wat de nieuwe Navigatie-API bereikt.

Bovendien verwerkt het bovenstaande geen achteruit/vooruit-navigatie. Daar is nog een evenement voor, "popstate" .

Persoonlijk heeft de History API vaak het gevoel dat deze op de een of andere manier kan helpen met deze mogelijkheden. Het heeft echter eigenlijk maar twee oppervlakten: reageren als de gebruiker op Terug of Vooruit drukt in zijn browser, plus het pushen en vervangen van URL's. Het heeft geen analogie met "navigate" , behalve als u bijvoorbeeld handmatig luisteraars instelt voor klikgebeurtenissen, zoals hierboven gedemonstreerd.

Beslissen hoe een navigatie moet worden afgehandeld

De navigateEvent bevat veel informatie over de navigatie waarmee u kunt beslissen hoe u met een bepaalde navigatie omgaat.

De belangrijkste eigenschappen zijn:

canIntercept
Als dit niet waar is, kunt u de navigatie niet onderscheppen. Cross-origin-navigatie en cross-document-traversals kunnen niet worden onderschept.
destination.url
Waarschijnlijk het belangrijkste stukje informatie waarmee u rekening moet houden bij het omgaan met de navigatie.
hashChange
Waar als de navigatie hetzelfde document is en de hash het enige deel van de URL is dat verschilt van de huidige URL. In moderne SPA's moet de hash bedoeld zijn om te linken naar verschillende delen van het huidige document. Dus als hashChange waar is, hoeft u deze navigatie waarschijnlijk niet te onderscheppen.
downloadRequest
Als dit waar is, is de navigatie gestart door een link met een download . In de meeste gevallen hoeft u dit niet te onderscheppen.
formData
Als dit niet nul is, maakt deze navigatie deel uit van een POST-formulierinzending. Zorg ervoor dat u hier rekening mee houdt bij het omgaan met de navigatie. Als u alleen GET-navigatie wilt afhandelen, vermijd dan het onderscheppen van navigatie waarbij formData niet null is. Zie het voorbeeld over het afhandelen van formulierinzendingen verderop in het artikel.
navigationType
Dit is er een van "reload" , "push" , "replace" of "traverse" . Als het "traverse" is, kan deze navigatie niet worden geannuleerd via preventDefault() .

De functie shouldNotIntercept die in het eerste voorbeeld wordt gebruikt, zou er bijvoorbeeld ongeveer zo uit kunnen zien:

function shouldNotIntercept(navigationEvent) {
  return (
    !navigationEvent.canIntercept ||
    // If this is just a hashChange,
    // just let the browser handle scrolling to the content.
    navigationEvent.hashChange ||
    // If this is a download,
    // let the browser perform the download.
    navigationEvent.downloadRequest ||
    // If this is a form submission,
    // let that go to the server.
    navigationEvent.formData
  );
}

Onderscheppen

Wanneer uw code intercept({ handler }) aanroept vanuit de "navigate" -listener, informeert deze de browser dat de pagina nu wordt voorbereid op de nieuwe, bijgewerkte status, en dat de navigatie enige tijd kan duren.

De browser begint met het vastleggen van de schuifpositie voor de huidige status, zodat deze eventueel later kan worden hersteld, en roept vervolgens de callback van uw handler op. Als uw handler een belofte retourneert (wat automatisch gebeurt met asynchrone functies ), vertelt die belofte de browser hoe lang de navigatie duurt en of deze succesvol is.

navigation.addEventListener('navigate', navigateEvent => {
  if (shouldNotIntercept(navigateEvent)) return;
  const url = new URL(navigateEvent.destination.url);

  if (url.pathname.startsWith('/articles/')) {
    navigateEvent.intercept({
      async handler() {
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
      },
    });
  }
});

Als zodanig introduceert deze API een semantisch concept dat de browser begrijpt: er vindt momenteel in de loop van de tijd een SPA-navigatie plaats, waardoor het document van een vorige URL en status naar een nieuwe verandert. Dit heeft een aantal potentiële voordelen, waaronder toegankelijkheid: browsers kunnen het begin, het einde of de mogelijke mislukking van een navigatie weergeven. Chrome activeert bijvoorbeeld de eigen laadindicator en stelt de gebruiker in staat te communiceren met de stopknop. (Dit gebeurt momenteel niet wanneer de gebruiker navigeert via de knoppen Terug/Vooruit, maar dat wordt binnenkort opgelost .)

Bij het onderscheppen van navigatie wordt de nieuwe URL van kracht net voordat de callback van uw handler wordt aangeroepen. Als u de DOM niet onmiddellijk bijwerkt, ontstaat er een periode waarin de oude inhoud samen met de nieuwe URL wordt weergegeven. Dit heeft invloed op zaken als de relatieve URL-resolutie bij het ophalen van gegevens of het laden van nieuwe subbronnen.

Een manier om de URL-wijziging uit te stellen wordt besproken op GitHub , maar over het algemeen wordt aanbevolen om de pagina onmiddellijk bij te werken met een soort tijdelijke aanduiding voor de binnenkomende inhoud:

navigation.addEventListener('navigate', navigateEvent => {
  if (shouldNotIntercept(navigateEvent)) return;
  const url = new URL(navigateEvent.destination.url);

  if (url.pathname.startsWith('/articles/')) {
    navigateEvent.intercept({
      async handler() {
        // The URL has already changed, so quickly show a placeholder.
        renderArticlePagePlaceholder();
        // Then fetch the real data.
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
      },
    });
  }
});

Dit voorkomt niet alleen problemen met de URL-resolutie, het voelt ook snel aan omdat u direct op de gebruiker reageert.

Afbreeksignalen

Omdat u asynchroon werk kunt doen in een intercept() handler, is het mogelijk dat de navigatie overbodig wordt. Dit gebeurt wanneer:

  • De gebruiker klikt op een andere link, of een code voert een andere navigatie uit. In dit geval wordt de oude navigatie verlaten ten gunste van de nieuwe navigatie.
  • De gebruiker klikt op de 'stop'-knop in de browser.

Om met al deze mogelijkheden om te gaan, bevat de gebeurtenis die aan de "navigate" -luisteraar wordt doorgegeven een signal , namelijk een AbortSignal . Zie Afbreekbaar ophalen voor meer informatie.

De korte versie is dat het in feite een object biedt dat een gebeurtenis activeert wanneer u uw werk moet stoppen. U kunt met name een AbortSignal doorgeven aan elke aanroep die u doet fetch() , waardoor netwerkverzoeken tijdens de vlucht worden geannuleerd als de navigatie wordt ondermijnd. Dit zal zowel de bandbreedte van de gebruiker besparen als de Promise afwijzen die wordt geretourneerd door fetch() , waardoor volgende code wordt verhinderd bij acties zoals het bijwerken van de DOM om een ​​nu ongeldige paginanavigatie weer te geven.

Hier is het vorige voorbeeld, maar met getArticleContent inline, en laat zien hoe AbortSignal kan worden gebruikt met fetch() :

navigation.addEventListener('navigate', navigateEvent => {
  if (shouldNotIntercept(navigateEvent)) return;
  const url = new URL(navigateEvent.destination.url);

  if (url.pathname.startsWith('/articles/')) {
    navigateEvent.intercept({
      async handler() {
        // The URL has already changed, so quickly show a placeholder.
        renderArticlePagePlaceholder();
        // Then fetch the real data.
        const articleContentURL = new URL(
          '/get-article-content',
          location.href
        );
        articleContentURL.searchParams.set('path', url.pathname);
        const response = await fetch(articleContentURL, {
          signal: navigateEvent.signal,
        });
        const articleContent = await response.json();
        renderArticlePage(articleContent);
      },
    });
  }
});

Scroll-afhandeling

Wanneer u een navigatie intercept() , zal de browser proberen het scrollen automatisch af te handelen.

Voor navigatie naar een nieuw geschiedenisitem (wanneer navigationEvent.navigationType "push" of "replace" is), betekent dit dat u probeert naar het gedeelte te scrollen dat wordt aangegeven door het URL-fragment (het bit na de # ), of dat u de scroll naar boven opnieuw instelt van de pagina.

Voor herladen en traversals betekent dit dat de schuifpositie wordt hersteld naar de plaats waar deze de laatste keer was dat dit geschiedenisitem werd weergegeven.

Standaard gebeurt dit zodra de door uw handler geretourneerde belofte is opgelost, maar als het zinvol is om eerder te scrollen, kunt u navigateEvent.scroll() aanroepen:

navigation.addEventListener('navigate', navigateEvent => {
  if (shouldNotIntercept(navigateEvent)) return;
  const url = new URL(navigateEvent.destination.url);

  if (url.pathname.startsWith('/articles/')) {
    navigateEvent.intercept({
      async handler() {
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
        navigateEvent.scroll();

        const secondaryContent = await getSecondaryContent(url.pathname);
        addSecondaryContent(secondaryContent);
      },
    });
  }
});

Als alternatief kunt u zich volledig afmelden voor automatische scrollverwerking door de scroll van intercept() in te stellen op "manual" :

navigateEvent.intercept({
  scroll: 'manual',
  async handler() {
    // …
  },
});

Focusbehandeling

Zodra de door uw handler geretourneerde belofte is opgelost, zal de browser het eerste element met het autofocus attribuut focussen, of het <body> -element als geen enkel element dat attribuut heeft.

U kunt zich afmelden voor dit gedrag door de focusReset optie van intercept() in te stellen op "manual" :

navigateEvent.intercept({
  focusReset: 'manual',
  async handler() {
    // …
  },
});

Succes- en mislukkingsgebeurtenissen

Wanneer uw intercept() -handler wordt aangeroepen, zal een van de volgende twee dingen gebeuren:

  • Als de geretourneerde Promise wordt vervuld (of als u intercept() niet hebt aangeroepen), zal de navigatie-API "navigatesuccess" afvuren met een Event .
  • Als de geretourneerde Promise afwijst, activeert de API "navigateerror" met een ErrorEvent .

Dankzij deze gebeurtenissen kan uw code op een gecentraliseerde manier omgaan met succes of falen. U kunt bijvoorbeeld omgaan met succes door een eerder weergegeven voortgangsindicator te verbergen, zoals deze:

navigation.addEventListener('navigatesuccess', event => {
  loadingIndicator.hidden = true;
});

Of u krijgt mogelijk een foutmelding bij een fout:

navigation.addEventListener('navigateerror', event => {
  loadingIndicator.hidden = true; // also hide indicator
  showMessage(`Failed to load page: ${event.message}`);
});

De gebeurtenislistener "navigateerror" , die een ErrorEvent ontvangt, is bijzonder handig omdat deze gegarandeerd fouten ontvangt van uw code die een nieuwe pagina instelt. U kunt eenvoudigweg await fetch() in de wetenschap dat als het netwerk niet beschikbaar is, de fout uiteindelijk zal worden doorgestuurd naar "navigateerror" .

navigation.currentEntry biedt toegang tot het huidige item. Dit is een object dat beschrijft waar de gebruiker zich nu bevindt. Dit item bevat de huidige URL, metagegevens die kunnen worden gebruikt om dit item in de loop van de tijd te identificeren, en de door de ontwikkelaar verstrekte status.

De metagegevens omvatten key , een unieke tekenreekseigenschap van elk item die het huidige item en zijn slot vertegenwoordigt. Deze sleutel blijft hetzelfde, zelfs als de URL of de status van het huidige item verandert. Het zit nog steeds in hetzelfde slot. Omgekeerd, als een gebruiker op Terug drukt en vervolgens dezelfde pagina opnieuw opent, verandert key omdat dit nieuwe item een ​​nieuw slot creëert.

Voor een ontwikkelaar is key handig omdat u met de navigatie-API de gebruiker rechtstreeks naar een item met een overeenkomende sleutel kunt navigeren. Je kunt het vasthouden, zelfs in de status van andere items, zodat je gemakkelijk tussen pagina's kunt springen.

// On JS startup, get the key of the first loaded page
// so the user can always go back there.
const {key} = navigation.currentEntry;
backToHomeButton.onclick = () => navigation.traverseTo(key);

// Navigate away, but the button will always work.
await navigation.navigate('/another_url').finished;

Staat

De Navigatie-API brengt het begrip 'status' naar voren, dit is door de ontwikkelaar verstrekte informatie die permanent wordt opgeslagen in het huidige geschiedenisitem, maar die niet direct zichtbaar is voor de gebruiker. Dit is zeer vergelijkbaar met, maar verbeterd ten opzichte van, history.state in de History API.

In de navigatie-API kunt u de methode .getState() van het huidige item (of een ander item) aanroepen om een ​​kopie van de status ervan te retourneren:

console.log(navigation.currentEntry.getState());

Standaard is dit undefined .

Staat instellen

Hoewel statusobjecten kunnen worden gemuteerd, worden deze wijzigingen niet opgeslagen bij de geschiedenisinvoer, dus:

const state = navigation.currentEntry.getState();
console.log(state.count); // 1
state.count++;
console.log(state.count); // 2
// But:
console.info(navigation.currentEntry.getState().count); // will still be 1

De juiste manier om de status in te stellen is tijdens scriptnavigatie:

navigation.navigate(url, {state: newState});
// Or:
navigation.reload({state: newState});

Waarbij newState elk kloonbaar object kan zijn.

Als u de status van het huidige item wilt bijwerken, kunt u het beste een navigatie uitvoeren die het huidige item vervangt:

navigation.navigate(location.href, {state: newState, history: 'replace'});

Vervolgens kan uw "navigate" -gebeurtenislistener deze wijziging oppikken via navigateEvent.destination :

navigation.addEventListener('navigate', navigateEvent => {
  console.log(navigateEvent.destination.getState());
});

Status synchroon bijwerken

Over het algemeen is het beter om de status asynchroon bij te werken via navigation.reload({state: newState}) , waarna uw "navigate" -listener die status kan toepassen. Soms is de statuswijziging echter al volledig toegepast tegen de tijd dat uw code ervan hoort, bijvoorbeeld wanneer de gebruiker een <details> -element omschakelt, of de gebruiker de status van een formulierinvoer wijzigt. In deze gevallen wilt u wellicht de status bijwerken, zodat deze wijzigingen behouden blijven tijdens herladen en traversals. Dit is mogelijk met updateCurrentEntry() :

navigation.updateCurrentEntry({state: newState});

Er is ook een evenement waar u over deze wijziging kunt horen:

navigation.addEventListener('currententrychange', () => {
  console.log(navigation.currentEntry.getState());
});

Maar als u merkt dat u reageert op statuswijzigingen in "currententrychange" , is het mogelijk dat u uw statusoverdragende code opsplitst of zelfs dupliceert tussen de "navigate" -gebeurtenis en de "currententrychange" -gebeurtenis, terwijl navigation.reload({state: newState}) waarmee u het op één plek kunt afhandelen.

Staat versus URL-parameters

Omdat state een gestructureerd object kan zijn, is het verleidelijk om het voor de hele applicatiestatus te gebruiken. In veel gevallen is het echter beter om die status in de URL op te slaan.

Als u verwacht dat de status behouden blijft wanneer de gebruiker de URL deelt met een andere gebruiker, slaat u deze op in de URL. Anders is het statusobject de betere optie.

Toegang tot alle vermeldingen

De "huidige invoer" is echter niet alles. De API biedt ook een manier om toegang te krijgen tot de volledige lijst met vermeldingen waar een gebruiker doorheen heeft genavigeerd tijdens het gebruik van uw site via de aanroep navigation.entries() , die een momentopname van de vermeldingen retourneert. Dit kan worden gebruikt om bijvoorbeeld een andere gebruikersinterface weer te geven op basis van hoe de gebruiker naar een bepaalde pagina heeft genavigeerd, of om gewoon terug te kijken naar de vorige URL's of hun status. Dit is onmogelijk met de huidige History API.

U kunt ook luisteren naar een "dispose" gebeurtenis op individuele NavigationHistoryEntry 's, die wordt geactiveerd wanneer het item niet langer deel uitmaakt van de browsergeschiedenis. Dit kan gebeuren als onderdeel van de algemene opruiming, maar ook tijdens het navigeren. Als u bijvoorbeeld 10 plaatsen teruggaat en vervolgens vooruit navigeert, worden die 10 geschiedenisitems verwijderd.

Voorbeelden

De gebeurtenis "navigate" wordt geactiveerd voor alle soorten navigatie, zoals hierboven vermeld. (Er staat eigenlijk een lange bijlage in de specificatie van alle mogelijke typen.)

Hoewel het voor veel sites het meest voorkomende geval is wanneer de gebruiker op een <a href="..."> klikt, zijn er twee opmerkelijke, complexere navigatietypen die de moeite waard zijn om te bespreken.

Programmatische navigatie

De eerste is programmatische navigatie, waarbij navigatie wordt veroorzaakt door een methodeaanroep in uw client-side code.

U kunt navigation.navigate('/another_page') overal in uw code aanroepen om een ​​navigatie te veroorzaken. Dit wordt afgehandeld door de gecentraliseerde gebeurtenislistener die is geregistreerd op de "navigate" -listener, en uw gecentraliseerde luisteraar wordt synchroon gebeld.

Dit is bedoeld als een verbeterde aggregatie van oudere methoden zoals location.assign() en Friends, plus de methoden pushState() en replaceState() van de History API.

De methode navigation.navigate() retourneert een object dat twee Promise -instanties bevat in { committed, finished } . Hierdoor kan de aanroeper wachten totdat de transitie is 'vastgelegd' (de zichtbare URL is gewijzigd en een nieuwe NavigationHistoryEntry beschikbaar is) of 'voltooid' (alle beloften die door intercept({ handler }) worden geretourneerd, zijn voltooid - of afgewezen vanwege falen of worden ondermijnd door een andere navigatie).

De navigate heeft ook een optieobject, waarin u het volgende kunt instellen:

  • state : de status voor het nieuwe geschiedenisitem, zoals beschikbaar via de .getState() -methode op de NavigationHistoryEntry .
  • history : kan worden ingesteld op "replace" om het huidige geschiedenisitem te vervangen.
  • info : een object dat moet worden doorgegeven aan de navigatiegebeurtenis via navigateEvent.info .

info kan met name nuttig zijn om bijvoorbeeld een bepaalde animatie aan te duiden die ervoor zorgt dat de volgende pagina verschijnt. (Het alternatief zou kunnen zijn om een ​​globale variabele in te stellen of deze op te nemen als onderdeel van de #hash. Beide opties zijn een beetje lastig.) Deze info wordt met name niet opnieuw afgespeeld als een gebruiker later navigatie veroorzaakt, bijvoorbeeld via zijn Terug- en Knoppen Vooruit. In feite zal het in die gevallen altijd undefined zijn.

Demo van openen van links of rechts

navigation heeft ook een aantal andere navigatiemethoden, die allemaal een object retourneren dat { committed, finished } bevat. Ik heb al traverseTo() genoemd (dat een key accepteert die een specifiek item in de geschiedenis van de gebruiker aangeeft) en navigate() . Het bevat ook back() , forward() en reload() . Deze methoden worden allemaal afgehandeld (net als navigate() door de gecentraliseerde gebeurtenislistener "navigate" .

Formulierinzendingen

Ten tweede is het indienen van HTML- <form> via POST een speciaal type navigatie, en de navigatie-API kan dit onderscheppen. Hoewel het een extra payload bevat, wordt de navigatie nog steeds centraal afgehandeld door de "navigate" -luisteraar.

Het indienen van formulieren kan worden gedetecteerd door te zoeken naar de eigenschap formData op NavigateEvent . Hier is een voorbeeld dat eenvoudig elke formulierinzending omzet in een formulier dat op de huidige pagina blijft via fetch() :

navigation.addEventListener('navigate', navigateEvent => {
  if (navigateEvent.formData && navigateEvent.canIntercept) {
    // User submitted a POST form to a same-domain URL
    // (If canIntercept is false, the event is just informative:
    // you can't intercept this request, although you could
    // likely still call .preventDefault() to stop it completely).

    navigateEvent.intercept({
      // Since we don't update the DOM in this navigation,
      // don't allow focus or scrolling to reset:
      focusReset: 'manual',
      scroll: 'manual',
      handler() {
        await fetch(navigateEvent.destination.url, {
          method: 'POST',
          body: navigateEvent.formData,
        });
        // You could navigate again with {history: 'replace'} to change the URL here,
        // which might indicate "done"
      },
    });
  }
});

Wat ontbreekt?

Ondanks de gecentraliseerde aard van de gebeurtenislistener "navigate" , activeert de huidige navigatie-API-specificatie "navigate" niet bij de eerste keer laden van een pagina. En voor sites die Server Side Rendering (SSR) voor alle statussen gebruiken, kan dit prima zijn: uw server kan de juiste beginstatus retourneren, wat de snelste manier is om inhoud bij uw gebruikers te krijgen. Maar sites die code aan de clientzijde gebruiken om hun pagina's te maken, moeten mogelijk een extra functie maken om hun pagina te initialiseren.

Een andere opzettelijke ontwerpkeuze van de Navigatie-API is dat deze alleen binnen één enkel frame werkt, dat wil zeggen de pagina op het hoogste niveau, of een enkel specifiek <iframe> . Dit heeft een aantal interessante implicaties die verder worden gedocumenteerd in de specificaties , maar die in de praktijk de verwarring bij ontwikkelaars zullen verminderen. De vorige History API heeft een aantal verwarrende randgevallen, zoals ondersteuning voor frames, en de opnieuw ontworpen Navigatie-API verwerkt deze randgevallen vanaf het begin.

Ten slotte bestaat er nog geen consensus over het programmatisch wijzigen of herschikken van de lijst met vermeldingen waar de gebruiker doorheen heeft genavigeerd. Dit wordt momenteel besproken , maar een optie zou kunnen zijn om alleen verwijderingen toe te staan: historische vermeldingen of "alle toekomstige vermeldingen". Dit laatste zou een tijdelijke staat mogelijk maken. Als ontwikkelaar zou ik bijvoorbeeld:

  • stel de gebruiker een vraag door naar de nieuwe URL of status te navigeren
  • de gebruiker toestaan ​​zijn werk te voltooien (of terug te gaan)
  • verwijder een geschiedenisvermelding na voltooiing van een taak

Dit zou perfect kunnen zijn voor tijdelijke modals of interstitials: de nieuwe URL is iets waarvan een gebruiker het terug-gebaar kan gebruiken om te vertrekken, maar hij kan dan niet per ongeluk naar Vooruit gaan om de URL opnieuw te openen (omdat de invoer is verwijderd). Dit is gewoon niet mogelijk met de huidige History API.

Probeer de navigatie-API

De Navigatie-API is beschikbaar in Chrome 102 zonder vlaggen. Je kunt ook een demo van Domenic Denicola uitproberen .

Hoewel de klassieke History API eenvoudig lijkt, is deze niet erg goed gedefinieerd en kent hij een groot aantal problemen rond hoekgevallen en de manier waarop deze in browsers verschillend is geïmplementeerd. We hopen dat u overweegt feedback te geven over de nieuwe navigatie-API.

Referenties

Dankbetuigingen

Met dank aan Thomas Steiner , Domenic Denicola en Nate Chapin voor het beoordelen van dit bericht. Hero-afbeelding van Unsplash , door Jeremy Zero .