Moderne routering aan de clientzijde: de navigatie-API

Standaardisatie van client-side routing via een geheel nieuwe API die de bouw van single-page applicaties volledig vernieuwt.

Jake Archibald
Jake Archibald

Browser Support

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

Source

Single-page-applicaties (SPA's) worden gedefinieerd door een kernfunctie: ze herschrijven hun inhoud dynamisch wanneer de gebruiker met de site interacteert, in plaats van de standaardmethode waarbij geheel nieuwe pagina's van de server worden geladen.

Hoewel SPA's deze functionaliteit al via de History API konden bieden (of in beperkte gevallen door het #hash-gedeelte van de site aan te passen), is het een omslachtige API die al lang vóór SPA's de norm waren ontwikkeld – en het web schreeuwt om een ​​compleet nieuwe aanpak. De Navigation API is een voorgestelde API die deze ruimte volledig vernieuwt, in plaats van simpelweg de ruwe randjes van de History API te repareren. ( 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. Voor het technische voorstel, zie het conceptrapport in de WICG-repository .

Voorbeeldgebruik

Om de Navigatie API te gebruiken, begint u met het toevoegen van een "navigate" -listener aan het globale navigation . Deze gebeurtenis is fundamenteel gecentraliseerd : deze wordt geactiveerd voor alle soorten navigatie, ongeacht of de gebruiker een actie uitvoert (zoals klikken op een link, een formulier verzenden of terug- en vooruitgaan) of wanneer de navigatie programmatisch wordt geactiveerd (d.w.z. via de code van uw site). In de meeste gevallen zorgt dit ervoor dat uw code het standaardgedrag van de browser voor die actie overschrijft. Voor SPA's betekent dit waarschijnlijk dat de gebruiker op dezelfde pagina blijft en de content van de site wordt geladen of gewijzigd.

Een NavigateEvent wordt doorgegeven aan de "navigate" -listener die informatie over de navigatie bevat, zoals de bestemmings-URL, en waarmee u op één centrale plek op de navigatie kunt reageren. Een eenvoudige "navigate" -listener 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 navigeren:

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

In dit voorbeeld wordt intercept() aangeroepen bij de gebeurtenis. De browser roept uw handler callback aan, die de volgende status van uw site moet configureren. Dit creëert een overgangsobject, navigation.transition , dat andere code kan gebruiken om de voortgang van de navigatie te volgen.

Zowel intercept() als preventDefault() zijn meestal toegestaan, maar er zijn gevallen waarin ze niet aangeroepen kunnen worden. Je kunt navigatie niet afhandelen via intercept() als de navigatie een cross-origin navigatie is. En je kunt een navigatie niet annuleren via preventDefault() als de gebruiker op de Terug- of Vooruit-knop in zijn browser drukt; je zou je gebruikers niet op je site moeten kunnen vastzetten. (Dit wordt besproken op GitHub .)

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

Waarom nog een evenement aan het platform toevoegen?

Een "navigate" event listener centraliseert de verwerking van URL-wijzigingen binnen een SPA. Dit is een lastige opgave met oudere API's. Als je ooit de routing voor je eigen SPA hebt geschreven met behulp van de History API, heb je misschien code zoals deze 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 volledig. Links kunnen op uw pagina verschijnen en verdwijnen, en dat is niet de enige manier waarop gebruikers door pagina's kunnen navigeren. Ze kunnen bijvoorbeeld een formulier invullen of een imagemap gebruiken. Uw pagina kan hier mogelijk mee omgaan, maar er is een lange lijst met mogelijkheden die eenvoudigweg vereenvoudigd kunnen worden – iets wat de nieuwe Navigatie API mogelijk maakt.

Bovendien ondersteunt bovenstaande geen terug-/vooruitnavigatie. Daar is een ander evenement voor: "popstate" .

Persoonlijk denk ik dat de History API hier wel wat aan zou kunnen bijdragen. Het heeft echter eigenlijk maar twee toepassingen: reageren als de gebruiker op Terug of Vooruit drukt in zijn browser, en URL's pushen en vervangen. Het is niet te vergelijken met "navigate" , behalve als je bijvoorbeeld handmatig listeners instelt voor klikgebeurtenissen, zoals hierboven gedemonstreerd.

Beslissen hoe u een navigatie wilt afhandelen

De navigateEvent bevat veel informatie over de navigatie. U kunt deze informatie gebruiken om te bepalen hoe u met een specifieke navigatie wilt omgaan.

De belangrijkste eigenschappen zijn:

canIntercept
Als dit onjuist is, kunt u de navigatie niet onderscheppen. Navigatie tussen verschillende bronnen en het doorlopen van documenten kan niet worden onderschept.
destination.url
Waarschijnlijk de belangrijkste informatie waarmee u rekening moet houden bij het navigeren.
hashChange
True 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 zou de hash gebruikt moeten worden voor links naar verschillende delen van het huidige document. Dus als hashChange true is, hoeft u deze navigatie waarschijnlijk niet te onderscheppen.
downloadRequest
Als dit het geval is, werd de navigatie gestart via 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. Houd hier rekening mee bij het verwerken van de navigatie. Als u alleen GET-navigaties wilt verwerken, vermijd dan het onderscheppen van navigatie waarbij formData niet nul is. Zie het voorbeeld over het verwerken van formulierinzendingen verderop in dit artikel.
navigationType
Dit is een van de volgende opties: "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 werd gebruikt, zou er bijvoorbeeld 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, wordt de browser geïnformeerd dat de pagina nu wordt voorbereid voor de nieuwe, bijgewerkte status en dat de navigatie enige tijd kan duren.

De browser begint met het vastleggen van de scrollpositie voor de huidige status, zodat deze later eventueel kan worden hersteld. Vervolgens roept hij de callback van uw handler aan. 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);
      },
    });
  }
});

Deze API introduceert een semantisch concept dat de browser begrijpt: een SPA-navigatie vindt momenteel plaats en verandert het document in de loop van de tijd van een eerdere URL en status naar een nieuwe. Dit heeft een aantal potentiële voordelen, waaronder toegankelijkheid: browsers kunnen het begin, het einde of een mogelijke fout van een navigatie weergeven. Chrome activeert bijvoorbeeld de ingebouwde laadindicator en stelt de gebruiker in staat om te interageren 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 vlak voordat uw handler callback wordt aangeroepen. Als u de DOM niet direct bijwerkt, ontstaat er een periode waarin de oude content 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 subresources.

Er wordt op GitHub gesproken over een manier om de URL-wijziging uit te stellen, maar over het algemeen is het aan te raden om de pagina direct bij te werken met een soort tijdelijke aanduiding voor de binnenkomende content:

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);
      },
    });
  }
});

Hiermee voorkom je niet alleen problemen met de URL-resolutie, het voelt ook snel aan omdat je direct reageert op de gebruiker.

Afbreken signalen

Omdat je asynchroon werk kunt doen in een intercept() handler, is het mogelijk dat de navigatie redundant 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 een van deze mogelijkheden om te gaan, bevat de gebeurtenis die aan de "navigate" -listener wordt doorgegeven een signal , namelijk AbortSignal . Zie Abortable fetch voor meer informatie.

Kort gezegd biedt het een object dat een gebeurtenis activeert wanneer u uw werk moet stoppen. U kunt bijvoorbeeld een AbortSignal meegeven aan alle aanroepen van fetch() , waardoor actieve netwerkverzoeken worden geannuleerd als de navigatie wordt onderbroken. Dit bespaart de gebruiker bandbreedte en verwerpt de Promise die door fetch() wordt geretourneerd, waardoor volgende code geen acties kan uitvoeren zoals het bijwerken van de DOM om een ​​nu ongeldige paginanavigatie weer te geven.

Hier is het vorige voorbeeld, maar dan met getArticleContent erin verwerkt, om te laten 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() , probeert de browser het scrollen automatisch af te handelen.

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

Bij herladen en doorkruisen betekent dit dat de scrollpositie wordt hersteld naar de positie waar deze de vorige keer was toen dit geschiedenisitem werd weergegeven.

Standaard gebeurt dit zodra de belofte die door uw handler wordt geretourneerd, 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);
      },
    });
  }
});

U kunt er ook voor kiezen om de automatische scrollverwerking volledig uit te schakelen door de scroll van intercept() in te stellen op "manual" :

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

Focusbehandeling

Zodra de belofte die door uw handler wordt geretourneerd, wordt opgelost, zal de browser de focus leggen op het eerste element met het autofocus kenmerk ingesteld, of op het <body> -element als geen enkel element dat kenmerk heeft.

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

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

Succes- en mislukkingsgebeurtenissen

Wanneer uw intercept() handler wordt aangeroepen, gebeuren er twee dingen:

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

Deze gebeurtenissen zorgen ervoor dat je code centraal kan omgaan met succes of falen. Je kunt bijvoorbeeld succes verwerken door een eerder weergegeven voortgangsindicator te verbergen, zoals hier:

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

Of er kan een foutmelding verschijnen als de bewerking mislukt:

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

De "navigateerror" -gebeurtenislistener, die een ErrorEvent ontvangt, is bijzonder handig omdat deze gegarandeerd fouten ontvangt van je code die een nieuwe pagina opzet. Je kunt gewoon await fetch() in de wetenschap dat als het netwerk niet beschikbaar is, de fout uiteindelijk naar "navigateerror" wordt doorgestuurd.

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

De metadata bevatten key , een unieke tekenreekseigenschap van elk item die het huidige item en de bijbehorende slot vertegenwoordigt. Deze key blijft hetzelfde, zelfs als de URL of status van het huidige item verandert. Het bevindt zich nog steeds in hetzelfde slot. Omgekeerd, als een gebruiker op Back drukt en vervolgens dezelfde pagina opnieuw opent, verandert key omdat dit nieuwe item een ​​nieuw slot creëert.

Voor een ontwikkelaar is key nuttig omdat je met de Navigatie API de gebruiker direct naar een item met een overeenkomende sleutel kunt navigeren. Je kunt de sleutel behouden, 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 introduceert een begrip van "status", wat door de ontwikkelaar verstrekte informatie is 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 Geschiedenis API.

In de Navigatie-API kunt u de .getState() -methode van de huidige invoer (of een willekeurige invoer) aanroepen om een ​​kopie van de status ervan te retourneren:

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

Standaard is dit undefined .

Status instellen

Hoewel statusobjecten kunnen worden gemuteerd, worden deze wijzigingen niet opgeslagen in de geschiedenisvermelding, 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 hiervan op de hoogte is, bijvoorbeeld wanneer de gebruiker een <details> -element aan- of uitzet, of wanneer de gebruiker de status van een formulierinvoer wijzigt. In deze gevallen kunt u de status bijwerken, zodat deze wijzigingen behouden blijven tijdens herladen en doorkruisen. Dit is mogelijk met updateCurrentEntry() :

navigation.updateCurrentEntry({state: newState});

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

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

Maar als u merkt dat u reageert op statuswijzigingen in "currententrychange" , splitst of dupliceert u mogelijk uw code voor de statusafhandeling tussen de gebeurtenis "navigate" en de gebeurtenis "currententrychange" , terwijl u dit navigation.reload({state: newState}) op één plek kunt afhandelen.

Status- versus URL-parameters

Omdat status een gestructureerd object kan zijn, is het verleidelijk om het voor al je applicatiestatussen 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 met een andere gebruiker deelt, slaat u deze op in de URL. Anders is het statusobject de betere optie.

Toegang tot alle items

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

Je kunt ook luisteren naar een "dispose" -gebeurtenis voor individuele NavigationHistoryEntry 's, die wordt geactiveerd wanneer het item niet langer deel uitmaakt van de browsergeschiedenis. Dit kan gebeuren als onderdeel van een algemene opschoning, maar ook tijdens het navigeren. Als je bijvoorbeeld 10 plaatsen teruggaat en vervolgens weer vooruit navigeert, worden die 10 items uit de geschiedenis verwijderd.

Voorbeelden

De "navigate" -gebeurtenis wordt geactiveerd voor alle soorten navigatie, zoals hierboven vermeld. (Er is zelfs een uitgebreide bijlage in de specificatie van alle mogelijke typen.)

Voor veel sites is het meest voorkomende navigatietype dat de gebruiker op <a href="..."> klikt, maar er zijn twee opvallende, complexere navigatietypen die de moeite waard zijn om te bespreken.

Programmatische navigatie

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

Je kunt navigation.navigate('/another_page') overal in je code aanroepen om navigatie te activeren. Dit wordt afgehandeld door de gecentraliseerde event listener die geregistreerd is op de "navigate" listener, en je gecentraliseerde listener wordt synchroon aangeroepen.

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 tot de overgang is "commit" (de zichtbare URL is gewijzigd en er is een nieuwe NavigationHistoryEntry beschikbaar) of "finished" (alle promises die door intercept({ handler }) worden geretourneerd, zijn voltooid – of afgewezen, omdat ze zijn mislukt of door een andere navigatie zijn ingehaald).

De navigate heeft ook een options-object, waar 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 om door te geven aan de navigate-gebeurtenis via navigateEvent.info .

info kan met name nuttig zijn om bijvoorbeeld een specifieke 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 wat onhandig.) Deze info wordt met name niet herhaald als een gebruiker later navigatie activeert, bijvoorbeeld via de knoppen Terug en Vooruit. Sterker nog, in die gevallen is de info altijd undefined .

Demo van openen van links of rechts

navigation kent ook een aantal andere navigatiemethoden, die allemaal een object retourneren dat { committed, finished } bevat. Ik heb traverseTo() (dat een key accepteert die een specifieke vermelding in de gebruikersgeschiedenis aangeeft) en navigate() al genoemd. Het omvat ook back() , forward() en reload() . Deze methoden worden allemaal – net als navigate() – afgehandeld door de gecentraliseerde "navigate" -gebeurtenislistener.

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" -listener.

Formulierinzendingen kunnen worden gedetecteerd door te zoeken naar de formData eigenschap in de NavigateEvent . Hier is een voorbeeld dat elke formulierinzending eenvoudigweg omzet in een formulier dat op de huidige pagina blijft staan ​​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 er?

Ondanks het gecentraliseerde karakter van de "navigate" -gebeurtenislistener, activeert de huidige Navigation API-specificatie "navigate" niet bij de eerste keer dat een pagina wordt geladen. En voor sites die Server Side Rendering (SSR) voor alle statussen gebruiken, kan dit prima zijn: uw server zou de juiste beginstatus kunnen retourneren, wat de snelste manier is om content bij uw gebruikers te krijgen. Maar sites die client-side code gebruiken om hun pagina's te maken, moeten mogelijk een extra functie creëren om hun pagina te initialiseren.

Een andere bewuste ontwerpkeuze van de Navigatie API is dat deze alleen binnen één frame werkt: de hoofdpagina of één specifieke <iframe> . Dit heeft een aantal interessante implicaties die verder worden beschreven in de specificatie , maar in de praktijk zal het de verwarring bij ontwikkelaars verminderen. De vorige History API kende een aantal verwarrende randgevallen, zoals de ondersteuning voor frames, en de vernieuwde Navigatie API behandelt deze randgevallen vanaf het begin.

Ten slotte is er nog geen consensus over het programmatisch aanpassen of herschikken van de lijst met items waar de gebruiker doorheen heeft genavigeerd. Dit wordt momenteel besproken , maar een optie zou kunnen zijn om alleen verwijderingen toe te staan: ofwel historische items ofwel "alle toekomstige items". Dat laatste zou een tijdelijke status toestaan. Als ontwikkelaar zou ik bijvoorbeeld het volgende kunnen doen:

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

Dit zou perfect kunnen zijn voor tijdelijke modals of interstitials: de nieuwe URL is iets waar een gebruiker met het Terug-gebaar de pagina kan verlaten, maar hij of zij kan de pagina vervolgens niet per ongeluk Verder openen (omdat het item is verwijderd). Dit is simpelweg 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 deze een groot aantal problemen met specifieke gevallen en de verschillende implementaties in verschillende browsers. We hopen dat u feedback wilt geven over de nieuwe Navigation API.

Referenties

Dankbetuigingen

Dank aan Thomas Steiner , Domenic Denicola en Nate Chapin voor het beoordelen van dit bericht.