आधुनिक क्लाइंट-साइड रूटिंग: नेविगेशन एपीआई

एकदम नए एपीआई की मदद से, क्लाइंट-साइड रूटिंग को स्टैंडर्ड बनाना. इससे सिंगल-पेज ऐप्लिकेशन बनाने का तरीका पूरी तरह से बदल जाता है.

Browser Support

  • Chrome: 102.
  • Edge: 102.
  • Firefox: 147.
  • Safari: 26.2.

Source

एक पेज वाले ऐप्लिकेशन (एसपीए) की मुख्य सुविधा यह होती है कि उपयोगकर्ता के साइट से इंटरैक्ट करने पर, ये अपने कॉन्टेंट को डाइनैमिक तरीके से फिर से लिखते हैं. ऐसा सर्वर से पूरी तरह से नए पेजों को लोड करने की डिफ़ॉल्ट प्रोसेस के बजाय किया जाता है.

एसपीए, History API की मदद से आपको यह सुविधा देते हैं. हालांकि, कुछ मामलों में वे साइट के #हैश वाले हिस्से में बदलाव करके भी यह सुविधा देते हैं. यह पुराना एपीआई है, जिसे एसपीए के चलन में आने से पहले ही डेवलप कर लिया गया था. अब वेब को एक नए तरीके की ज़रूरत है. Navigation API एक प्रस्तावित एपीआई है. यह इस स्पेस को पूरी तरह से बदल देता है. यह History API की कमियों को ठीक करने की कोशिश नहीं करता. (उदाहरण के लिए, स्क्रोल रीस्टोरेशन ने History API को फिर से बनाने के बजाय उसे पैच किया.)

इस पोस्ट में, नेविगेशन एपीआई के बारे में खास जानकारी दी गई है. तकनीकी प्रस्ताव पढ़ने के लिए, WICG रिपॉज़िटरी में ड्राफ़्ट रिपोर्ट देखें.

इस्तेमाल से जुड़ा उदाहरण

Navigation API का इस्तेमाल करने के लिए, सबसे पहले ग्लोबल navigation ऑब्जेक्ट पर "navigate" लिसनर जोड़ें. यह इवेंट, सेंट्रलाइज़्ड होता है.यह सभी तरह के नेविगेशन के लिए ट्रिगर होगा. भले ही, उपयोगकर्ता ने कोई कार्रवाई की हो (जैसे, किसी लिंक पर क्लिक करना, कोई फ़ॉर्म सबमिट करना या पीछे और आगे जाना) या नेविगेशन को प्रोग्राम के हिसाब से ट्रिगर किया गया हो (यानी, आपकी साइट के कोड के ज़रिए). ज़्यादातर मामलों में, इससे आपका कोड, उस कार्रवाई के लिए ब्राउज़र के डिफ़ॉल्ट व्यवहार को बदल सकता है. एसपीए के लिए, इसका मतलब है कि उपयोगकर्ता को उसी पेज पर रखना और साइट के कॉन्टेंट को लोड करना या बदलना.

"navigate" लिसनर को एक NavigateEvent पास किया जाता है. इसमें नेविगेशन के बारे में जानकारी होती है. जैसे, डेस्टिनेशन यूआरएल. इससे आपको एक ही जगह पर नेविगेशन का जवाब देने की अनुमति मिलती है. एक बेसिक "navigate" लिसनर कुछ ऐसा दिख सकता है:

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

नेविगेशन को इन दो तरीकों में से किसी एक तरीके से मैनेज किया जा सकता है:

  • नेविगेशन को मैनेज करने के लिए, intercept({ handler }) को कॉल करना (जैसा कि ऊपर बताया गया है).
  • कॉलिंग preventDefault(), जिससे नेविगेशन पूरी तरह से रद्द हो सकता है.

इस उदाहरण में, इवेंट पर intercept() को कॉल किया गया है. ब्राउज़र, आपके handler कॉलबैक को कॉल करता है. इसे आपकी साइट की अगली स्थिति को कॉन्फ़िगर करना चाहिए. इससे एक ट्रांज़िशन ऑब्जेक्ट, navigation.transition बन जाएगा. इसका इस्तेमाल अन्य कोड, नेविगेशन की प्रोग्रेस को ट्रैक करने के लिए कर सकते हैं.

आम तौर पर, intercept() और preventDefault() दोनों को कॉल करने की अनुमति होती है. हालांकि, कुछ मामलों में इन्हें कॉल नहीं किया जा सकता. अगर नेविगेशन क्रॉस-ऑरिजिन नेविगेशन है, तो intercept() के ज़रिए नेविगेशन को मैनेज नहीं किया जा सकता. अगर उपयोगकर्ता अपने ब्राउज़र में 'वापस जाएं' या 'आगे जाएं' बटन दबा रहा है, तो preventDefault() के ज़रिए नेविगेशन रद्द नहीं किया जा सकता. आपको अपनी साइट पर उपयोगकर्ताओं को ट्रैप नहीं करना चाहिए. (इस बारे में GitHub पर चर्चा हो रही है.)

भले ही, नेविगेशन को रोका या इंटरसेप्ट न किया जा सके, लेकिन "navigate" इवेंट अब भी ट्रिगर होगा. यह जानकारी देने वाला होता है. इसलिए, आपका कोड Analytics इवेंट को लॉग कर सकता है. इससे यह पता चलता है कि कोई उपयोगकर्ता आपकी साइट छोड़ रहा है.

प्लैटफ़ॉर्म पर कोई दूसरा इवेंट क्यों जोड़ना चाहिए?

"navigate" इवेंट लिसनर, एसपीए में यूआरएल में होने वाले बदलावों को मैनेज करने का काम करता है. पुराने एपीआई का इस्तेमाल करके, ऐसा करना मुश्किल है. अगर आपने कभी History API का इस्तेमाल करके, अपने एसपीए के लिए राउटिंग लिखी है, तो आपने इस तरह का कोड जोड़ा होगा:

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

यह ठीक है, लेकिन इसमें पूरी जानकारी नहीं है. आपके पेज पर लिंक दिख सकते हैं और हट भी सकते हैं. साथ ही, पेजों पर नेविगेट करने के लिए, लिंक ही एकमात्र तरीका नहीं है. उदाहरण के लिए, वे कोई फ़ॉर्म सबमिट कर सकते हैं या इमेज मैप का इस्तेमाल कर सकते हैं. आपका पेज इन समस्याओं को हल कर सकता है. हालांकि, ऐसी कई समस्याएं हैं जिन्हें आसानी से हल किया जा सकता है. नया Navigation API ऐसा ही करता है.

इसके अलावा, ऊपर दिए गए कोड में बैक/फ़ॉरवर्ड नेविगेशन को हैंडल नहीं किया जाता. इसके लिए, एक और इवेंट है "popstate".

मुझे लगता है कि History API, इन संभावनाओं को पूरा करने में काफ़ी हद तक मदद कर सकता है. हालांकि, इसमें सिर्फ़ दो तरह की सुविधाएं होती हैं: उपयोगकर्ता के ब्राउज़र में 'वापस जाएं' या 'आगे बढ़ें' बटन दबाने पर जवाब देना. इसके अलावा, यूआरएल को पुश करना और बदलना. इसमें "navigate" के जैसी कोई सुविधा नहीं होती. हालांकि, अगर आपने क्लिक इवेंट के लिए लिसनर को मैन्युअल तरीके से सेट अप किया है, तो ऐसा किया जा सकता है. जैसे, ऊपर दिखाया गया है.

नेविगेशन को मैनेज करने का तरीका तय करना

navigateEvent में नेविगेशन के बारे में काफ़ी जानकारी होती है. इसका इस्तेमाल करके, यह तय किया जा सकता है कि किसी नेविगेशन से कैसे निपटा जाए.

मुख्य प्रॉपर्टी ये हैं:

canIntercept
अगर यह वैल्यू गलत है, तो नेविगेशन को इंटरसेप्ट नहीं किया जा सकता. क्रॉस-ऑरिजिन नेविगेशन और क्रॉस-डॉक्यूमेंट ट्रैवर्सल को इंटरसेप्ट नहीं किया जा सकता.
destination.url
नेविगेशन को मैनेज करते समय, इस जानकारी पर ध्यान देना सबसे ज़रूरी होता है.
hashChange
अगर नेविगेशन एक ही दस्तावेज़ में होता है और हैश, यूआरएल का वह हिस्सा होता है जो मौजूदा यूआरएल से अलग होता है, तो यह वैल्यू सही होती है. मॉडर्न एसपीए में, हैश का इस्तेमाल मौजूदा दस्तावेज़ के अलग-अलग हिस्सों को लिंक करने के लिए किया जाना चाहिए. इसलिए, अगर hashChange सही है, तो शायद आपको इस नेविगेशन को इंटरसेप्ट करने की ज़रूरत नहीं है.
downloadRequest
अगर यह सही है, तो नेविगेशन को download एट्रिब्यूट वाले लिंक से शुरू किया गया था. ज़्यादातर मामलों में, आपको इसे इंटरसेप्ट करने की ज़रूरत नहीं होती.
formData
अगर यह शून्य नहीं है, तो यह नेविगेशन, POST फ़ॉर्म सबमिट करने का हिस्सा है. नेविगेशन को मैनेज करते समय, इस बात का ध्यान रखें. अगर आपको सिर्फ़ GET नेविगेशन को हैंडल करना है, तो उन नेविगेशन को इंटरसेप्ट न करें जहां formData शून्य नहीं है. फ़ॉर्म सबमिट करने से जुड़े उदाहरण के बारे में जानने के लिए, इस लेख में आगे पढ़ें.
navigationType
यह "reload", "push", "replace" या "traverse" में से कोई एक है. अगर यह "traverse" है, तो preventDefault() के ज़रिए इस नेविगेशन को रद्द नहीं किया जा सकता.

उदाहरण के लिए, पहले उदाहरण में इस्तेमाल किया गया shouldNotIntercept फ़ंक्शन कुछ ऐसा हो सकता है:

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

इंटरसेप्ट करना

जब आपका कोड, "navigate" लिसनर के अंदर से intercept({ handler }) को कॉल करता है, तो यह ब्राउज़र को बताता है कि अब वह पेज को नई, अपडेट की गई स्थिति के लिए तैयार कर रहा है. साथ ही, नेविगेशन में कुछ समय लग सकता है.

ब्राउज़र, मौजूदा स्थिति के लिए स्क्रोल की पोज़िशन को कैप्चर करता है, ताकि बाद में इसे वापस लाया जा सके. इसके बाद, यह आपके handler कॉलबैक को कॉल करता है. अगर आपका handler कोई प्रॉमिस दिखाता है (जो एसिंक फ़ंक्शन के साथ अपने-आप होता है), तो वह प्रॉमिस ब्राउज़र को बताता है कि नेविगेशन में कितना समय लगता है और यह सफल हुआ है या नहीं.

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

इसलिए, यह एपीआई एक सिमैंटिक कॉन्सेप्ट पेश करता है, जिसे ब्राउज़र समझता है: फ़िलहाल, एसपीए नेविगेशन हो रहा है. समय के साथ, यह दस्तावेज़ को पिछले यूआरएल और स्थिति से बदलकर नए यूआरएल और स्थिति में बदल देता है. इससे कई फ़ायदे मिलते हैं. जैसे, सुलभता: ब्राउज़र, नेविगेशन के शुरू होने, खत्म होने या संभावित तौर पर काम न करने की जानकारी दे सकते हैं. उदाहरण के लिए, Chrome अपना नेटिव लोडिंग इंडिकेटर चालू करता है और उपयोगकर्ता को 'बंद करें' बटन से इंटरैक्ट करने की अनुमति देता है. (फ़िलहाल, जब उपयोगकर्ता 'वापस जाएं' या 'आगे बढ़ें' बटन का इस्तेमाल करता है, तब ऐसा नहीं होता. हालांकि, इस समस्या को जल्द ही ठीक कर दिया जाएगा.)

नेविगेशन को इंटरसेप्ट करते समय, नया यूआरएल आपके handler कॉलबैक को कॉल किए जाने से ठीक पहले लागू होगा. अगर आपने डीओएम को तुरंत अपडेट नहीं किया, तो इस दौरान पुराना कॉन्टेंट नए यूआरएल के साथ दिखता है. इससे डेटा फ़ेच करते समय या नए सब-रिसोर्स लोड करते समय, रिलेटिव यूआरएल रिज़ॉल्यूशन जैसी चीज़ों पर असर पड़ता है.

यूआरएल में बदलाव करने में देरी करने के तरीके के बारे में GitHub पर चर्चा की जा रही है. हालांकि, आम तौर पर यह सुझाव दिया जाता है कि आने वाले कॉन्टेंट के लिए किसी तरह के प्लेसहोल्डर के साथ पेज को तुरंत अपडेट करें:

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

इससे यूआरएल रिज़ॉल्यूशन से जुड़ी समस्याएं नहीं होती हैं. साथ ही, यह तेज़ी से काम करता है, क्योंकि उपयोगकर्ता को तुरंत जवाब मिलता है.

सिग्नल बंद करना

intercept() हैंडलर में एसिंक्रोनस तरीके से काम किया जा सकता है. इसलिए, नेविगेशन के काम न करने की समस्या हो सकती है. ऐसा तब होता है, जब:

  • जब उपयोगकर्ता किसी दूसरे लिंक पर क्लिक करता है या कोई कोड, नेविगेशन की कोई दूसरी कार्रवाई करता है. इस मामले में, नए नेविगेशन के लिए पुराने नेविगेशन को बंद कर दिया जाता है.
  • उपयोगकर्ता, ब्राउज़र में मौजूद 'बंद करें' बटन पर क्लिक करता है.

इनमें से किसी भी स्थिति से निपटने के लिए, "navigate" लिसनर को पास किए गए इवेंट में signal प्रॉपर्टी होती है, जो कि एक AbortSignal है. ज़्यादा जानकारी के लिए, Abortable fetch देखें.

संक्षेप में कहें, तो यह एक ऐसा ऑब्जेक्ट उपलब्ध कराता है जो काम रोकने के लिए इवेंट ट्रिगर करता है. खास तौर पर, AbortSignal को कॉल करते समय, fetch() को पास किया जा सकता है. इससे, नेविगेशन के बीच में आने वाले नेटवर्क अनुरोध रद्द हो जाएंगे. इससे उपयोगकर्ता का बैंडविथ बचेगा. साथ ही, fetch() से मिले Promise को अस्वीकार कर दिया जाएगा. इससे, बाद में आने वाले कोड को ऐसी कार्रवाइयां करने से रोका जा सकेगा. जैसे, DOM को अपडेट करके, अब अमान्य हो चुके पेज नेविगेशन को दिखाना.

यहां पिछले उदाहरण को getArticleContent के साथ इनलाइन किया गया है. इससे पता चलता है कि AbortSignal को 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);
      },
    });
  }
});

स्क्रोल मैनेज करना

जब किसी नेविगेशन को intercept() किया जाता है, तो ब्राउज़र अपने-आप स्क्रोल करने की कोशिश करेगा.

नई हिस्ट्री एंट्री पर नेविगेट करने के लिए (जब navigationEvent.navigationType "push" या "replace" हो), इसका मतलब है कि यूआरएल फ़्रैगमेंट (# के बाद वाला हिस्सा) से दिखाए गए हिस्से पर स्क्रोल करने की कोशिश करना या स्क्रोल को पेज के सबसे ऊपर रीसेट करना.

फिर से लोड करने और ट्रैवर्सल के लिए, इसका मतलब है कि स्क्रोल की स्थिति को उस जगह पर वापस लाना जहां यह इतिहास की एंट्री पिछली बार दिखाई गई थी.

डिफ़ॉल्ट रूप से, यह तब होता है, जब handler से मिला प्रॉमिस पूरा हो जाता है. हालांकि, अगर आपको पहले ही स्क्रोल करना है, तो 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);
      },
    });
  }
});

इसके अलावा, intercept() के scroll विकल्प को "manual" पर सेट करके, ऑटोमैटिक स्क्रोल हैंडलिंग की सुविधा से पूरी तरह ऑप्ट आउट किया जा सकता है:

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

फ़ोकस मैनेज करना

handler से मिला प्रॉमिस पूरा होने के बाद, ब्राउज़र उस पहले एलिमेंट पर फ़ोकस करेगा जिसमें autofocus एट्रिब्यूट सेट किया गया है. अगर किसी भी एलिमेंट में यह एट्रिब्यूट नहीं है, तो ब्राउज़र <body> एलिमेंट पर फ़ोकस करेगा.

intercept() के focusReset विकल्प को "manual" पर सेट करके, इस सुविधा से ऑप्ट आउट किया जा सकता है:

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

सफल और असफल इवेंट

intercept() हैंडलर को कॉल करने पर, इनमें से कोई एक काम होगा:

  • अगर लौटाया गया Promise पूरा होता है (या आपने intercept() को कॉल नहीं किया है), तो Navigation API, Event के साथ "navigatesuccess" को ट्रिगर करेगा.
  • अगर Promise को अस्वीकार कर दिया जाता है, तो एपीआई ErrorEvent के साथ "navigateerror" को ट्रिगर करेगा.

इन इवेंट की मदद से, आपका कोड एक ही जगह पर सफलता या असफलता से निपट सकता है. उदाहरण के लिए, इस तरह से प्रोग्रेस इंडिकेटर को छिपाकर, टास्क पूरा होने की जानकारी दी जा सकती है:

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

इसके अलावा, फ़ेल होने पर गड़बड़ी का मैसेज भी दिखाया जा सकता है:

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

"navigateerror" इवेंट लिसनर, ErrorEvent को स्वीकार करता है. यह खास तौर पर तब काम आता है, जब यह पक्का करना हो कि नए पेज को सेट अप करने वाले आपके कोड से जुड़ी सभी गड़बड़ियां इसे मिलें. आपको सिर्फ़ await fetch() करना है. अगर नेटवर्क उपलब्ध नहीं है, तो गड़बड़ी को "navigateerror" पर भेज दिया जाएगा.

navigation.currentEntry से मौजूदा एंट्री को ऐक्सेस किया जा सकता है. यह एक ऑब्जेक्ट है. इससे पता चलता है कि उपयोगकर्ता फ़िलहाल कहां है. इस एंट्री में मौजूदा यूआरएल, मेटाडेटा (जिसका इस्तेमाल समय के साथ इस एंट्री की पहचान करने के लिए किया जा सकता है), और डेवलपर की ओर से दी गई स्थिति शामिल होती है.

मेटाडेटा में key शामिल होता है. यह हर एंट्री की यूनीक स्ट्रिंग प्रॉपर्टी होती है. यह मौजूदा एंट्री और उसके स्लॉट को दिखाता है. मौजूदा एंट्री का यूआरएल या स्थिति बदलने पर भी, यह कुंजी नहीं बदलती. यह अब भी उसी स्लॉट में है. इसके उलट, अगर कोई उपयोगकर्ता 'वापस जाएं' बटन दबाता है और फिर उसी पेज को दोबारा खोलता है, तो key बदल जाएगा. ऐसा इसलिए, क्योंकि इस नई एंट्री से एक नया स्लॉट बन जाता है.

डेवलपर के लिए key फ़ायदेमंद है, क्योंकि Navigation API की मदद से, उपयोगकर्ता को सीधे तौर पर मैचिंग कुंजी वाली एंट्री पर ले जाया जा सकता है. इससे आपको एक पेज से दूसरे पेज पर आसानी से जाने में मदद मिलती है.

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

राज्य

Navigation API, "स्टेट" की जानकारी देता है. यह डेवलपर की दी गई जानकारी होती है. इसे मौजूदा इतिहास की एंट्री में सेव किया जाता है. हालांकि, यह जानकारी सीधे तौर पर उपयोगकर्ता को नहीं दिखती है. यह History API में मौजूद history.state से काफ़ी मिलता-जुलता है, लेकिन इससे बेहतर है.

Navigation API में, मौजूदा एंट्री (या किसी भी एंट्री) के .getState() तरीके को कॉल करके, उसकी स्थिति की कॉपी वापस पाई जा सकती है:

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

डिफ़ॉल्ट रूप से, यह undefined पर सेट होगा.

सेटिंग की स्थिति

स्टेट ऑब्जेक्ट में बदलाव किया जा सकता है. हालांकि, उन बदलावों को इतिहास की एंट्री के साथ वापस सेव नहीं किया जाता. इसलिए:

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

स्क्रिप्ट नेविगेशन के दौरान, स्थिति सेट करने का सही तरीका यह है:

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

यहां newState कोई भी क्लोन किया जा सकने वाला ऑब्जेक्ट हो सकता है.

अगर आपको मौजूदा एंट्री की स्थिति अपडेट करनी है, तो मौजूदा एंट्री की जगह नेविगेशन करना सबसे अच्छा तरीका है:

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

इसके बाद, आपका "navigate" इवेंट लिसनर, navigateEvent.destination के ज़रिए इस बदलाव को पिक अप कर सकता है:

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

स्टेट को सिंक्रोनस तरीके से अपडेट करना

आम तौर पर, navigation.reload({state: newState}) के ज़रिए एसिंक्रोनस तरीके से स्थिति को अपडेट करना बेहतर होता है. इसके बाद, आपका "navigate" लिसनर उस स्थिति को लागू कर सकता है. हालांकि, कभी-कभी ऐसा होता है कि आपके कोड को राज्य में हुए बदलाव के बारे में पता चलने से पहले ही, बदलाव पूरी तरह से लागू हो जाता है. जैसे, जब उपयोगकर्ता किसी <details> एलिमेंट को टॉगल करता है या फ़ॉर्म इनपुट की स्थिति बदलता है. ऐसे मामलों में, आपको स्थिति अपडेट करनी पड़ सकती है, ताकि रीलोड और ट्रैवर्सल के दौरान इन बदलावों को बनाए रखा जा सके. updateCurrentEntry() का इस्तेमाल करके ऐसा किया जा सकता है:

navigation.updateCurrentEntry({state: newState});

इस बदलाव के बारे में जानने के लिए, एक इवेंट भी है:

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

हालांकि, अगर आपको "currententrychange" में स्टेट में हुए बदलावों पर प्रतिक्रिया देनी है, तो हो सकता है कि आपको "navigate" इवेंट और "currententrychange" इवेंट के बीच, स्टेट को मैनेज करने वाले कोड को बांटना पड़े या डुप्लीकेट करना पड़े. वहीं, navigation.reload({state: newState}) की मदद से, इसे एक ही जगह पर मैनेज किया जा सकता है.

स्टेट बनाम यूआरएल पैरामीटर

स्टेट एक स्ट्रक्चर्ड ऑब्जेक्ट हो सकता है. इसलिए, इसका इस्तेमाल अपने ऐप्लिकेशन की सभी स्टेट के लिए किया जा सकता है. हालांकि, कई मामलों में उस स्थिति को यूआरएल में सेव करना बेहतर होता है.

अगर आपको लगता है कि उपयोगकर्ता के यूआरएल को किसी दूसरे उपयोगकर्ता के साथ शेयर करने पर, स्थिति बनी रहनी चाहिए, तो उसे यूआरएल में सेव करें. इसके अलावा, स्टेट ऑब्जेक्ट बेहतर विकल्प है.

सभी एंट्री ऐक्सेस करना

हालांकि, "मौजूदा एंट्री" में सभी जानकारी शामिल नहीं होती. एपीआई, navigation.entries() कॉल के ज़रिए उन सभी एंट्री की सूची को ऐक्सेस करने का तरीका भी उपलब्ध कराता है जिन्हें उपयोगकर्ता ने आपकी साइट का इस्तेमाल करते समय नेविगेट किया है. यह कॉल, एंट्री का स्नैपशॉट ऐरे दिखाता है. इसका इस्तेमाल, उदाहरण के लिए, इस तरह किया जा सकता है: उपयोगकर्ता किसी पेज पर कैसे पहुंचा, इसके आधार पर अलग-अलग यूज़र इंटरफ़ेस (यूआई) दिखाना या सिर्फ़ पिछले यूआरएल या उनकी स्थितियों को देखना. मौजूदा History API की मदद से ऐसा नहीं किया जा सकता.

इसके अलावा, अलग-अलग NavigationHistoryEntry पर "dispose" इवेंट को भी सुना जा सकता है. यह इवेंट तब ट्रिगर होता है, जब एंट्री ब्राउज़र के इतिहास का हिस्सा नहीं रहती. ऐसा सामान्य तौर पर डेटा को हटाने के दौरान हो सकता है. हालांकि, ऐसा नेविगेट करते समय भी हो सकता है. उदाहरण के लिए, अगर आपने 10 पेजों पर वापस जाकर फिर से आगे के पेजों पर नेविगेट किया, तो इतिहास की वे 10 एंट्री मिट जाएंगी.

उदाहरण

ऊपर बताए गए तरीके से नेविगेट करने पर, "navigate" इवेंट ट्रिगर होता है. (असल में, सभी संभावित टाइप के स्पेसिफ़िकेशन में एक लंबा अपेंडिक्स होता है.)

ज़्यादातर साइटों पर, उपयोगकर्ता के <a href="..."> पर क्लिक करने का सबसे सामान्य मामला होता है. हालांकि, नेविगेशन के दो ऐसे टाइप हैं जो ज़्यादा जटिल हैं और इनके बारे में जानना ज़रूरी है.

प्रोग्राम के हिसाब से, अपने-आप होने वाली प्रोसेस का इस्तेमाल करके नेविगेट करना

पहला तरीका प्रोग्रामैटिक नेविगेशन है. इसमें नेविगेशन, क्लाइंट-साइड कोड में मौजूद किसी तरीके के कॉल की वजह से होता है.

नेविगेशन के लिए, अपने कोड में कहीं से भी navigation.navigate('/another_page') को कॉल किया जा सकता है. इसे "navigate" लिसनर पर रजिस्टर किया गया सेंट्रलाइज़्ड इवेंट लिसनर हैंडल करेगा. साथ ही, आपके सेंट्रलाइज़्ड लिसनर को सिंक्रोनस तरीके से कॉल किया जाएगा.

इसका मकसद, location.assign() और इससे मिलते-जुलते पुराने तरीकों के साथ-साथ History API के pushState() और replaceState() तरीकों को बेहतर तरीके से एग्रीगेट करना है.

navigation.navigate() तरीका, एक ऑब्जेक्ट दिखाता है. इस ऑब्जेक्ट में { committed, finished } में मौजूद दो Promise इंस्टेंस होते हैं. इससे, अनुरोध करने वाला व्यक्ति तब तक इंतज़ार कर सकता है, जब तक ट्रांज़िशन "कमिट" न हो जाए. इसका मतलब है कि दिखने वाला यूआरएल बदल गया है और नया NavigationHistoryEntry उपलब्ध है. इसके अलावा, वह तब तक इंतज़ार कर सकता है, जब तक ट्रांज़िशन "पूरा" न हो जाए. इसका मतलब है कि intercept({ handler }) से मिले सभी वादे पूरे हो गए हैं या किसी गड़बड़ी की वजह से या किसी दूसरे नेविगेशन से पहले ही खारिज कर दिए गए हैं.

navigate तरीके में एक विकल्प ऑब्जेक्ट भी होता है. इसमें ये सेट किए जा सकते हैं:

  • state: नई हिस्ट्री एंट्री की स्थिति, जो NavigationHistoryEntry पर .getState() तरीके से उपलब्ध है.
  • history: इसे "replace" पर सेट किया जा सकता है, ताकि मौजूदा इतिहास की एंट्री को बदला जा सके.
  • info: navigateEvent.info के ज़रिए, नेविगेट इवेंट को पास करने के लिए ऑब्जेक्ट.

खास तौर पर, info का इस्तेमाल, उदाहरण के लिए, किसी ऐसे ऐनिमेशन को दिखाने के लिए किया जा सकता है जिसकी वजह से अगला पेज दिखता है. (इसके बजाय, ग्लोबल वैरिएबल सेट किया जा सकता है या इसे #हैश के हिस्से के तौर पर शामिल किया जा सकता है. दोनों विकल्प थोड़े अजीब हैं.) ध्यान दें कि अगर कोई उपयोगकर्ता बाद में नेविगेशन करता है, तो यह info फिर से नहीं चलेगा. जैसे, 'वापस जाएं' और 'आगे जाएं' बटन के ज़रिए. दरअसल, ऐसे मामलों में यह हमेशा undefined होगा.

बाएं या दाएं से खोलने का डेमो

navigation में नेविगेशन के कई अन्य तरीके भी हैं. ये सभी तरीके, { committed, finished } वाला ऑब्जेक्ट दिखाते हैं. मैंने पहले ही traverseTo() (जो key स्वीकार करता है. यह उपयोगकर्ता के इतिहास में किसी खास एंट्री को दिखाता है) और navigate() के बारे में बता दिया है. इसमें back(), forward(), और reload() भी शामिल हैं. इन सभी तरीकों को navigate() की तरह, सेंट्रलाइज़ किए गए "navigate" इवेंट लिसनर से मैनेज किया जाता है.

फ़ॉर्म सबमिट करना

दूसरा, POST के ज़रिए एचटीएमएल <form> सबमिट करना एक खास तरह का नेविगेशन है. Navigation API इसे इंटरसेप्ट कर सकता है. इसमें अतिरिक्त पेलोड शामिल होता है. हालांकि, नेविगेशन को अब भी "navigate" लिसनर के ज़रिए मैनेज किया जाता है.

NavigateEvent पर formData प्रॉपर्टी देखकर, फ़ॉर्म सबमिट होने का पता लगाया जा सकता है. यहां एक उदाहरण दिया गया है, जिसमें 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"
      },
    });
  }
});

क्‍या अनुपलब्‍ध है?

"navigate" इवेंट लिसनर के सेंट्रलाइज़्ड होने के बावजूद, मौजूदा Navigation API स्पेसिफ़िकेशन, पेज के पहली बार लोड होने पर "navigate" को ट्रिगर नहीं करता है. साथ ही, जिन साइटों पर सभी स्थितियों के लिए सर्वर साइड रेंडरिंग (एसएसआर) का इस्तेमाल किया जाता है उनके लिए यह ठीक हो सकता है. आपका सर्वर, सही शुरुआती स्थिति दिखा सकता है. यह उपयोगकर्ताओं को कॉन्टेंट दिखाने का सबसे तेज़ तरीका है. हालांकि, जो साइटें अपने पेजों को बनाने के लिए क्लाइंट-साइड कोड का इस्तेमाल करती हैं उन्हें अपने पेज को शुरू करने के लिए, एक और फ़ंक्शन बनाना पड़ सकता है.

Navigation API को इस तरह से डिज़ाइन किया गया है कि यह सिर्फ़ एक फ़्रेम में काम करता है. इसका मतलब है कि यह सिर्फ़ टॉप-लेवल पेज या किसी खास <iframe> में काम करता है. इसके कई दिलचस्प पहलू हैं. इनके बारे में स्पेसिफ़िकेशन में ज़्यादा जानकारी दी गई है. हालांकि, इससे डेवलपर को होने वाली उलझन कम हो जाएगी. पिछले History API में कई ऐसे मामले हैं जिनमें भ्रम की स्थिति पैदा होती है. जैसे, फ़्रेम के लिए सहायता. वहीं, नए Navigation API में इन मामलों को शुरू से ही हैंडल किया जाता है.

आखिर में, इस बात पर अब तक सहमति नहीं बनी है कि उपयोगकर्ता ने जिन एंट्री को नेविगेट किया है उनकी सूची को प्रोग्राम के हिसाब से बदला जाए या फिर से व्यवस्थित किया जाए. इस बारे में फ़िलहाल बातचीत चल रही है. हालांकि, एक विकल्प यह हो सकता है कि सिर्फ़ मिटाने की अनुमति दी जाए. जैसे, पुरानी एंट्री या "आने वाले समय की सभी एंट्री". बाद वाले विकल्प से, कुछ समय के लिए स्थिति को सेव किया जा सकेगा. उदाहरण के लिए, डेवलपर के तौर पर ये काम किए जा सकते हैं:

  • नए यूआरएल या स्थिति पर जाकर, उपयोगकर्ता से कोई सवाल पूछना
  • उपयोगकर्ता को अपना काम पूरा करने (या वापस जाने) की अनुमति दें
  • टास्क पूरा होने पर, इतिहास की एंट्री हटाना

यह सुविधा, कुछ समय के लिए दिखने वाले मोडल या इंटरस्टीशियल के लिए सबसे सही हो सकती है: नया यूआरएल ऐसा होता है कि उपयोगकर्ता 'वापस जाएं' जेस्चर का इस्तेमाल करके उसे छोड़ सकता है. हालांकि, इसके बाद वह गलती से भी उसे फिर से नहीं खोल सकता, क्योंकि एंट्री हटा दी गई है. फ़िलहाल, History API के साथ ऐसा नहीं किया जा सकता.

Navigation API आज़माएं

Navigation API, Chrome 102 में फ़्लैग के बिना उपलब्ध है. Domenic Denicola ने भी इसका डेमो दिया है. इसे भी आज़माएं.

क्लासिक History API का इस्तेमाल करना आसान लगता है. हालांकि, यह अच्छी तरह से तय नहीं किया गया है. साथ ही, इसमें कॉर्नर केस और अलग-अलग ब्राउज़र पर इसे लागू करने के तरीके से जुड़ी कई समस्याएं हैं. हमें उम्मीद है कि आप नए Navigation API के बारे में सुझाव/राय देंगे या शिकायत करेंगे.

रेफ़रंस

Acknowledgements

इस पोस्ट की समीक्षा करने के लिए, थॉमस स्टाइनर, डोमेनिक डेनिकोला, और नेट चैपिन का धन्यवाद.