Nowoczesny routing po stronie klienta: interfejs API nawigacji

Standaryzacja routingu po stronie klienta za pomocą zupełnie nowego interfejsu API, która całkowicie zastępuje tworzenie aplikacji jednostronicowych.

Obsługa przeglądarek

  • 102
  • 102
  • x
  • x

Źródło

Aplikacje jednostronicowe (SPA) definiują ich podstawową funkcję: dynamiczne przepisywanie treści w miarę interakcji użytkownika z witryną, zamiast korzystać z domyślnej metody ładowania całkowicie nowych stron z serwera.

Mimo że aplikacje jednostkowe mogły skorzystać z tej funkcji za pomocą interfejsu History API (lub w niektórych przypadkach, dostosowując fragment #hash witryny), jest to niezgodny interfejs API opracowany na długo, zanim aplikacje tego typu stały się normą, a internet apeluje o zupełnie nowe podejście. Interfejs Navigation API to proponowany interfejs API, który całkowicie remontuje tę przestrzeń, a nie po prostu próbuje naprawić drobne błędy interfejsu History API. Na przykład w przypadku aplikacji Scroll Restoration została wprowadzona poprawka History API, zamiast próbować wymyślić ją od nowa.

W tym poście szczegółowo opisujemy interfejs API nawigacji. Jeśli chcesz zapoznać się z propozycją techniczną, zapoznaj się z wersją roboczą raportu w repozytorium WICG.

Przykład użycia

Aby korzystać z interfejsu API nawigacji, najpierw dodaj odbiornik "navigate" do globalnego obiektu navigation. Zdarzenie to jest zasadniczo scentralizowane: uruchamia się w przypadku wszystkich typów nawigacji, niezależnie od tego, czy użytkownik wykonał czynność (np.kliknął link, przesłał formularz, przejście wstecz i do przodu) czy też gdy nawigacja jest uruchamiana programowo (np. przez kod witryny). W większości przypadków pozwala to Twojemu kodowi zastąpić domyślne zachowanie przeglądarki w przypadku tego działania. W przypadku aplikacji jednostronicowych oznacza to prawdopodobnie pozostawienie użytkownika na tej samej stronie oraz wczytywanie lub zmianę treści witryny.

Parametr NavigateEvent jest przekazywany do detektora "navigate", który zawiera informacje o nawigacji, takie jak docelowy adres URL. Umożliwia odpowiadanie na nią w jednym miejscu. Podstawowy detektor "navigate" może wyglądać tak:

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

Nawigację możesz obsługiwać na 2 sposoby:

  • Wywołuję metodę intercept({ handler }) (w sposób opisany powyżej) w celu obsługi nawigacji.
  • Dzwonię pod numer preventDefault(), co może całkowicie anulować nawigację.

Ten przykład wywołuje metodę intercept() w zdarzeniu. Przeglądarka wywołuje wywołanie zwrotne handler, które powinno skonfigurować następny stan witryny. Spowoduje to utworzenie obiektu przejścia navigation.transition, którego inny kod może używać do śledzenia postępu nawigacji.

Zarówno identyfikatory intercept(), jak i preventDefault() są zwykle dozwolone, ale czasami nie można ich wywołać. Nie możesz obsługiwać nawigacji za pomocą intercept(), jeśli jest to nawigacja między domenami. Nie możesz też anulować nawigacji w preventDefault(), jeśli użytkownik naciska przycisk Wstecz lub Dalej w przeglądarce. Nie powinno być możliwości uwięzienia użytkowników w witrynie. (Będzie to dyskutowane na GitHubie).

Nawet jeśli nie możesz zatrzymać ani przechwycić samej nawigacji, zdarzenie "navigate" zostanie uruchomione. Ma on charakter informacyjny – Twój kod może np. rejestrować zdarzenie Analytics wskazujące, że użytkownik opuszcza witrynę.

Po co dodawać kolejne wydarzenie na platformie?

Detektor zdarzeń "navigate" scentralizuje obsługę zmian adresów URL w aplikacji SPA. W przypadku starszych interfejsów API jest to trudna propozycja. Jeśli zdarzyło Ci się już za pomocą interfejsu History API utworzyć trasę routingu we własnej SPA, możesz dodać taki kod:

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

Jest to dozwolone, ale nie wyczerpuje grupy. Na stronie mogą pojawiać się i znikać linki i nie są one jedynym sposobem poruszania się użytkowników między stronami. Mogą na przykład przesłać formularz lub nawet użyć mapy zdjęcia. Twoja strona może sobie z tym poradzić, ale jest to na wiele sposobów, ale można je po prostu uprościć. Jest to coś, co zapewnia nowy interfejs API nawigacji.

Ponadto nie obsługuje ona nawigacji wstecz/do przodu. Przygotowaliśmy też inne wydarzenie na ten temat: "popstate".

Osobiście często wydaje się, że interfejs History API może w tym pomóc. Ma jednak tak naprawdę tylko 2 obszary: odpowiadanie, gdy użytkownik naciśnie Wstecz lub Dalej w przeglądarce oraz wypychanie i zastępowanie adresów URL. Nie ma to odpowiednika w przypadku "navigate", z wyjątkiem ręcznego konfigurowania detektorów zdarzeń kliknięcia, jak pokazano powyżej.

Podejmowanie decyzji o obsłudze nawigacji

navigateEvent zawiera dużo informacji o nawigacji, które pomogą Ci zdecydować, jak postępować w przypadku danej nawigacji.

Najważniejsze właściwości:

canIntercept
Jeśli ten parametr ma wartość false (fałsz), nie będzie można przechwycić nawigacji. Przechwycenia nawigacji między domenami i dokumentów nie są przechwytywane.
destination.url
To chyba najważniejsza informacja, którą należy wziąć pod uwagę podczas obsługi nawigacji.
hashChange
Prawda, jeśli nawigacja jest w tym samym dokumencie, a skrót jest jedyną częścią adresu URL, która różni się od bieżącego adresu URL. We współczesnych aplikacjach jednostkowych hasz powinien prowadzić do różnych części bieżącego dokumentu. Jeśli więc parametr hashChange ma wartość prawda, prawdopodobnie nie musisz przechwytywać tej nawigacji.
downloadRequest
Jeśli to prawda, nawigacja została zainicjowana przez link z atrybutem download. W większości przypadków nie musisz tego przechwytywać.
formData
Jeśli ta wartość nie ma wartości null, ta nawigacja jest częścią przesłania formularza POST. Pamiętaj, aby wziąć to pod uwagę podczas obsługiwania nawigacji. Jeśli chcesz obsługiwać tylko nawigację GET, unikaj przechwytywania nawigacji, w których formData nie ma wartości null. Przykład obsługi przesyłania formularzy znajdziesz w dalszej części artykułu.
navigationType
Jest to jedna z wartości "reload", "push", "replace" lub "traverse". Jeśli jest "traverse", nie można anulować tej nawigacji w preventDefault().

Na przykład funkcja shouldNotIntercept użyta w pierwszym przykładzie może być podobna do tej:

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

Przechwytywanie

Gdy Twój kod wywołuje intercept({ handler }) z detektora "navigate", informuje przeglądarkę, że przygotowuje ona stronę do nowego, zaktualizowanego stanu, a nawigacja może trochę potrwać.

Przeglądarka zaczyna od przechwycenia pozycji przewijania w bieżącym stanie, aby można ją później przywrócić, a potem wywołać wywołanie zwrotne handler. Jeśli handler zwraca obietnicę (co dzieje się automatycznie w przypadku async functions), informuje ona przeglądarkę o tym, jak długo trwa nawigacja i czy się powiodła.

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

W związku z tym ten interfejs API wprowadza koncepcję semantyczną, którą rozumie przeglądarka: obecnie zachodzą zmiany w obszarze SPA, które zmieniają dokument z poprzedniego adresu URL i jego stanu na nowy. Ma to wiele potencjalnych zalet, w tym ułatwienia dostępu: przeglądarki mogą wyświetlać początek, koniec lub potencjalne błędy nawigacji. Na przykład Chrome aktywuje natywny wskaźnik ładowania i umożliwia użytkownikowi interakcję z przyciskiem zatrzymania. Obecnie nie dzieje się tak, gdy użytkownik korzysta z przycisków Wstecz/Dalej, ale wkrótce zostanie to naprawione.

W przypadku przechwytywania nawigacji nowy adres URL zacznie obowiązywać tuż przed wywołaniem wywołania zwrotnego handler. Jeśli nie zaktualizujesz modelu DOM od razu, zostanie utworzony okres, w którym stara treść jest wyświetlana razem z nowym adresem URL. Ma to wpływ na względną jakość adresów URL podczas pobierania danych lub wczytywania nowych zasobów podrzędnych.

O sposobie opóźnienia zmiany adresu URL dyskutujemy na GitHubie, ale ogólnie zalecamy natychmiastowe zaktualizowanie strony za pomocą jakiegoś obiektu zastępczego dla treści przychodzących:

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

Pozwala to nie tylko uniknąć problemów z rozpoznawaniem adresów URL, ale też sprawia, że od razu odpowiadasz użytkownikom.

Przerwij sygnały

Ponieważ w module obsługi intercept() można wykonywać asynchroniczne działania, możliwe, że nawigacja stanie się nadmiarowa. Dzieje się tak, gdy:

  • Użytkownik klika inny link lub kod wykonuje inną nawigację. W takim przypadku następuje rezygnacja ze starej nawigacji.
  • Użytkownik klika przycisk „Zatrzymaj” w przeglądarce.

Aby uwzględnić te możliwości, zdarzenie przekazane do odbiornika "navigate" zawiera właściwość signal, która jest właściwością AbortSignal. Więcej informacji znajdziesz w sekcji Możliwość przerwania pobierania.

W skrócie udostępnia on obiekt, który wywołuje zdarzenie przy zatrzymaniu pracy. W szczególności możesz przekazywać AbortSignal wszystkim wywołaniam fetch(), co spowoduje anulowanie przesyłanych żądań sieciowych, jeśli nawigacja będzie wywłaszczana. Spowoduje to zmniejszenie przepustowości łącza użytkownika i odrzucenie żądania Promise zwracanego przez interfejs fetch(), uniemożliwiając żadne działanie z poniższego kodu takiemu jak zaktualizowanie DOM w celu pokazania nieprawidłowej nawigacji po stronie.

Oto poprzedni przykład, który po fragmencie getArticleContent pokazuje, jak używać elementu AbortSignal z elementem 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);
      },
    });
  }
});

Obsługa przewijania

Gdy użyjesz funkcji nawigacji intercept() podczas nawigacji, przeglądarka spróbuje automatycznie obsłużyć przewijanie.

Przejście do nowego wpisu w historii (gdy navigationEvent.navigationType ma wartość "push" lub "replace") oznacza próbę przewinięcia do części wskazywanej przez fragment adresu URL (bit po #) lub zresetowanie przewijania do góry strony.

W przypadku przeładowań i przemierzania oznacza to przywrócenie pozycji przewijania do miejsca, w którym ostatnio dany wpis historii był wyświetlany.

Domyślnie dzieje się to po rozwiązaniu obietnicy zwróconej przez handler, ale jeśli chcesz przewinąć wcześniej, możesz wywołać funkcję navigateEvent.scroll():

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

Możesz też całkowicie zrezygnować z automatycznej obsługi przewijania, ustawiając dla opcji scroll ustawienia intercept() wartość "manual":

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

Obsługa zaznaczenia

Gdy obietnica zwrócona przez handler zostanie zrealizowana, przeglądarka ustawi pierwszy element z ustawionym atrybutem autofocus lub element <body>, jeśli żaden z elementów nie ma tego atrybutu.

Możesz zrezygnować z tej funkcji, ustawiając opcję focusReset intercept() na "manual":

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

Zdarzenia powodzenia i niepowodzenia

Wywołanie modułu obsługi intercept() ma miejsce w jednej z 2 sytuacji:

  • Jeśli zwrócony Promise zostanie wypełniony (lub nie wywołano intercept()), interfejs API nawigacji uruchomi "navigatesuccess" z Event.
  • Jeśli zwrócona wartość Promise zostanie odrzucona, interfejs API uruchomi "navigateerror" z zasadą ErrorEvent.

Zdarzenia te umożliwiają kodowi reagowanie na sukces lub porażkę w scentralizowany sposób. Możesz na przykład rozwiązać problem, ukrywając wyświetlany wcześniej wskaźnik postępu:

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

W przypadku niepowodzenia może też wyświetlić się komunikat o błędzie:

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

Szczególnie przydatny jest detektor zdarzeń "navigateerror", który otrzymuje wartość ErrorEvent, ponieważ podczas konfigurowania nowej strony zwykle odbiera wszelkie błędy z kodu podczas konfigurowania nowej strony. Możesz po prostu await fetch() wiedząc, że jeśli sieć jest niedostępna, błąd zostanie w końcu skierowany do "navigateerror".

navigation.currentEntry zapewnia dostęp do bieżącego wpisu. Jest to obiekt, który opisuje, gdzie obecnie znajduje się użytkownik. Ten wpis zawiera bieżący adres URL, metadane, które mogą posłużyć do zidentyfikowania tego wpisu na przestrzeni czasu, oraz stan podany przez dewelopera.

Metadane obejmują key – unikalną właściwość ciągu znaków, która reprezentuje bieżący wpis i jego bok. Ten klucz pozostaje taki sam, nawet jeśli zmieni się adres URL lub stan bieżącego wpisu. Znajduje się ono nadal w tym samym miejscu. I odwrotnie, jeśli użytkownik naciśnie Wstecz, a następnie ponownie otworzy tę samą stronę, key zmieni się, ponieważ nowy wpis utworzy nowy boks.

Dla programistów interfejs key jest przydatny, ponieważ interfejs API nawigacji umożliwia bezpośrednie przechodzenie użytkownika do wpisu z pasującym kluczem. Możesz go trzymać nawet w stanach innych wpisów, aby łatwo przechodzić między stronami.

// 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;

Stan

Interfejs API nawigacji wyświetla pojęcie „stanu”, czyli podawane przez dewelopera informacje, które są trwale przechowywane w bieżącym wpisie historii, ale nie są bezpośrednio widoczne dla użytkownika. Funkcja jest bardzo podobna do funkcji history.state w interfejsie History API, ale została udoskonalona.

W interfejsie API nawigacji możesz wywołać metodę .getState() bieżącego wpisu (lub dowolnego wpisu), by zwrócić kopię jego stanu:

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

Domyślnie jest to undefined.

Stan ustawienia

Mimo że obiekty stanu mogą być mutowane, zmiany te nie są zapisywane z powrotem wraz ze wpisem w historii, więc:

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

Prawidłowy sposób ustawiania stanu to podczas nawigacji skryptu:

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

Gdzie newState może być dowolnym obiektem, który można skopiować.

Jeśli chcesz zaktualizować stan bieżącego wpisu, najlepiej wykonaj nawigację, która zastąpi bieżący wpis:

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

Następnie detektor zdarzeń "navigate" może wykryć tę zmianę za pomocą navigateEvent.destination:

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

Synchroniczne aktualizowanie stanu

Lepiej jest aktualizować stan asynchronicznie za pomocą metody navigation.reload({state: newState}). Wtedy odbiornik "navigate" może zastosować ten stan. Czasami jednak zmiana stanu zostaje w pełni zastosowana, gdy kod dowie się o niej, na przykład gdy użytkownik przełączy element <details> lub zmieni stan danych wejściowych formularza. W takich przypadkach warto zaktualizować stan, aby te zmiany były zachowywane podczas ponownego wczytywania i przemierzania. Jest to możliwe za pomocą updateCurrentEntry():

navigation.updateCurrentEntry({state: newState});

Możesz też poinformować o tej zmianie w innym wydarzeniu:

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

Jeśli jednak reagujesz na zmiany stanu w usłudze "currententrychange", być może dzielisz kod zarządzania stanowego między zdarzenie "navigate" i zdarzenie "currententrychange", a navigation.reload({state: newState}) umożliwiałby zarządzanie nim w jednym miejscu.

Stan a parametry adresu URL

Stan może być obiektem strukturalnym, więc kuszące jest używanie go w przypadku całego stanu aplikacji. Jednak w wielu przypadkach lepiej jest zapisać ten stan w adresie URL.

Jeśli uważasz, że stan zostanie zachowany, gdy użytkownik udostępni adres URL innemu użytkownikowi, zapisz go w adresie URL. W przeciwnym razie lepszym rozwiązaniem jest obiekt stanu.

Dostęp do wszystkich wpisów

„Obecny wpis” to jednak nie wszystko. Interfejs API zapewnia też dostęp do całej listy wpisów, które przeszedł użytkownik podczas korzystania z Twojej witryny, za pomocą wywołania navigation.entries(), które zwraca tablicę migawek wpisów. Można to wykorzystać, aby np. pokazać inny interfejs użytkownika w zależności od tego, jak użytkownik przeszedł na określoną stronę, lub tylko wrócić do poprzednich adresów URL lub ich stanów. Nie jest to możliwe przy obecnym interfejsie History API.

Możesz też nasłuchiwać zdarzenia "dispose" w poszczególnych elementach NavigationHistoryEntry, które jest wywoływane, gdy dany wpis nie jest już częścią historii przeglądania. Może się to zdarzyć w ramach ogólnego czyszczenia, ale też podczas korzystania z nawigacji. Jeśli np. cofniesz się o 10 miejsc, a potem przejdziesz dalej, te 10 wpisów z historii zostanie usuniętych.

Przykłady

Zdarzenie "navigate" jest uruchamiane w przypadku wszystkich typów nawigacji, jak wspomniano powyżej. (W specyfikacji są długie dodatki wszystkich możliwych typów).

W wielu witrynach najczęściej zdarza się, że użytkownik klika <a href="...">, ale warto tu wspomnieć o 2 ważniejszych, bardziej złożonych typach nawigacji.

Automatyzacja nawigacji

Pierwsza to automatyczna nawigacja, w której nawigowanie jest powodowane przez wywołanie metody w kodzie po stronie klienta.

Możesz wywołać navigation.navigate('/another_page') z dowolnego miejsca w kodzie, aby uruchomić nawigację. Będzie ona obsługiwana przez scentralizowany detektor zdarzeń zarejestrowany w detektorze "navigate", a scentralizowany detektor zostanie wywołany synchronicznie.

Ma to na celu ulepszoną agregację starszych metod, takich jak location.assign() i znajomych, oraz metod pushState() i replaceState() interfejsu History API.

Metoda navigation.navigate() zwraca obiekt zawierający 2 instancje Promise w obiekcie { committed, finished }. Dzięki temu wywołujący może czekać, aż przejście zostanie „zatwierdzone” (widoczny URL został zmieniony i dostępny jest nowy NavigationHistoryEntry) lub „zakończony” (wszystkie obietnice zwrócone przez interfejs intercept({ handler }) zostaną zrealizowane lub odrzucone z powodu błędu lub wykluczenia z innej nawigacji).

Metoda navigate ma też obiekt options, w którym możesz ustawić:

  • state: stan nowego wpisu w historii dostępny za pomocą metody .getState() na stronie NavigationHistoryEntry.
  • history: wartość "replace", która zastępuje bieżący wpis w historii.
  • info: obiekt do przekazania do zdarzenia nawigacji przez navigateEvent.info.

Tag info może być przydatny na przykład do oznaczania konkretnej animacji, która powoduje wyświetlenie kolejnej strony. (Możesz też ustawić zmienną globalną lub uwzględnić ją w #haszu. W obu przypadkach może to być trochę niezrozumiałe). info nie zostanie ponownie odtworzony, jeśli użytkownik później wywoła nawigację, np. używając przycisków Wstecz i Dalej. W takich przypadkach zawsze będzie to undefined.

Prezentacja otwierania od lewej lub z prawej strony

navigation ma też wiele innych metod nawigacji, które zwracają obiekt zawierający { committed, finished }. Wcześniej wspomniano o traverseTo() (akceptuje ona key, która oznacza konkretny wpis w historii użytkownika) i navigate(). Obejmuje też back(), forward() i reload(). Wszystkie te metody są obsługiwane przez scentralizowany detektor zdarzeń "navigate", tak jak navigate().

Przesyłanie formularzy

Po drugie, przesyłanie kodu HTML <form> za pomocą metody POST to specjalny typ nawigacji, który może przechwycić interfejs API nawigacji. Chociaż zawiera dodatkowy ładunek, nawigacja w dalszym ciągu jest obsługiwana centralnie przez detektor "navigate".

Przesłanie formularza można wykryć, wyszukując właściwość formData w NavigateEvent. Oto przykład, który powoduje zmianę dowolnego przesłanego formularza na taki, który pozostaje na bieżącej stronie przez 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"
      },
    });
  }
});

Czego brakuje?

Mimo że detektor zdarzeń "navigate" jest scentralizowany, obecna specyfikacja interfejsu API nawigacji nie uruchamia polecenia "navigate" przy pierwszym wczytaniu strony. W przypadku witryn, które korzystają z renderowania po stronie serwera (SSR) w przypadku wszystkich stanów, może to być w porządku – serwer może zwrócić prawidłowy stan początkowy, co jest najszybszym sposobem dostarczenia treści do użytkowników. Jednak w przypadku witryn korzystających z kodu po stronie klienta do tworzenia stron konieczne może być dodanie dodatkowej funkcji do inicjowania strony.

Innym zamierzonym wyborem interfejsu API nawigacji jest to, że działa on tylko w 1 ramce, czyli na stronie najwyższego poziomu lub w pojedynczym konkretnym elemencie <iframe>. Może to mieć szereg interesujących konsekwencji, które dokładnie opisaliśmy w specyfikacji, ale w praktyce pozwalają zmniejszyć dezorientację programistów. Poprzedni interfejs History API ma kilka niezrozumiałych przypadków skrajnych, takich jak obsługa ramek, a odświeżony interfejs Navigation API od razu obsługuje takie przypadki skrajne.

Nie ma jeszcze zgody co do programowego modyfikowania lub zmiany kolejności wpisów, które przeszedł użytkownik. Obecnie dyskutujemy, ale jedną z opcji może być zezwolenie tylko na usuwanie: wpisy historyczne lub „wszystkie przyszłe wpisy”. To drugie ustawienie zezwalało na stan tymczasowy. Na przykład jako programista mogę:

  • Zadaj użytkownikowi pytanie, przechodząc do nowego adresu URL lub stanu
  • Pozwól użytkownikowi wykonać pracę (lub wrócić do poprzedniego)
  • usuwanie wpisu historii po ukończeniu zadania

Może to być idealne rozwiązanie w przypadku tymczasowych reklam modalnych i pełnoekranowych: nowy adres URL to funkcja, z której użytkownik może wyjść, używając gestu Wstecz, ale nie może przypadkowo przejść do przodu, aby ponownie otworzyć stronę (ponieważ wpis został usunięty). Nie jest to możliwe w przypadku obecnego interfejsu History API.

Wypróbuj interfejs API nawigacji

Interfejs API nawigacji jest dostępny w Chrome 102 bez flag. Możesz też wypróbować wersję demonstracyjną autorstwa Domenica Denicoli.

Chociaż klasyczny interfejs History API wydaje się jasny, nie jest zbyt dobrze zdefiniowany i zawiera duże problemy w przypadkach, w których występuje wiele problemów, a także sposób jego odmiennego zaimplementowania w różnych przeglądarkach. Mamy nadzieję, że podzielisz się opinią na temat nowego interfejsu API nawigacji.

Odniesienia

Podziękowania

Dziękujemy Thomas Steiner, Domenic Denicola i Nate Chapin za przeczytanie tego posta. Baner powitalny z serii Unsplash autorstwa Jeremy'ego Zero.