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

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

ब्राउज़र के इस्तेमाल से जुड़ी सहायता

  • Chrome: 102.
  • Edge: 102.
  • Firefox: यह सुविधा काम नहीं करती.
  • Safari: यह सुविधा काम नहीं करती.

सोर्स

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

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

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

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

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

NavigateEvent को "navigate" लिसनर को पास किया जाता है. इसमें नेविगेशन की जानकारी होती है, जैसे कि डेस्टिनेशन यूआरएल. साथ ही, इससे आपको नेविगेशन के लिए एक ही जगह से जवाब देने की सुविधा मिलती है. "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));

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

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

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

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

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

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

canIntercept
अगर यह गलत है, तो नेविगेशन को इंटरसेप्ट नहीं किया जा सकता. क्रॉस-ऑरिजिन नेविगेशन और क्रॉस-दस्तावेज़ ट्रैवल को इंटरसेप्ट नहीं किया जा सकता.
destination.url
यह नेविगेशन को हैंडल करने के लिए सबसे ज़रूरी जानकारी है.
hashChange
अगर नेविगेशन एक ही दस्तावेज़ में है और यूआरएल का सिर्फ़ हैश हिस्सा मौजूदा यूआरएल से अलग है, तो यह वैल्यू 'सही' होगी. आधुनिक एसपीए में, हैश को मौजूदा दस्तावेज़ के अलग-अलग हिस्सों से लिंक करने के लिए इस्तेमाल किया जाना चाहिए. इसलिए, अगर hashChange सही है, तो हो सकता है कि आपको इस नेविगेशन को इंटरसेप्ट करने की ज़रूरत न पड़े.
downloadRequest
अगर यह सही है, तो नेविगेशन की शुरुआत download एट्रिब्यूट वाले लिंक से हुई थी. ज़्यादातर मामलों में, आपको इसकी जांच करने की ज़रूरत नहीं होती.
formData
अगर यह शून्य नहीं है, तो यह नेविगेशन, POST फ़ॉर्म सबमिशन का हिस्सा है. नेविगेशन को मैनेज करते समय, इस बात का ध्यान रखें. अगर आपको सिर्फ़ जीईटी नेविगेशन मैनेज करना है, तो उन नेविगेशन को इंटरसेप्ट करने से बचें जहां 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 होती है. ज़्यादा जानकारी के लिए, फ़ेच को बीच में रोकना लेख पढ़ें.

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

यहां दिया गया पिछला उदाहरण दिया गया है, लेकिन इसमें getArticleContent की लाइन है. इसमें दिखाया गया है कि fetch() के साथ AbortSignal का इस्तेमाल कैसे किया जा सकता है:

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() को कॉल नहीं किया है), तो नेविगेशन एपीआई, 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 फ़ंक्शन काफ़ी काम का है. इसकी मदद से, नेविगेशन एपीआई की मदद से उपयोगकर्ता को सीधे मैच करने वाली कुंजी वाली एंट्री पर ले जाया जा सकता है. आप एक पेज से दूसरे पेज पर आसानी से जाने के लिए, बाकी एंट्री की स्थितियों में भी इसे होल्ड पर रख सकते हैं.

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

स्थिति

नेविगेशन एपीआई, &quot;स्टेटस&quot; की जानकारी दिखाता है. यह डेवलपर की दी गई जानकारी होती है, जो इतिहास की मौजूदा एंट्री में हमेशा सेव रहती है. हालांकि, यह जानकारी उपयोगकर्ता को सीधे तौर पर नहीं दिखती. यह 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() कॉल की मदद से, आपकी साइट पर नेविगेट करते समय नेविगेट किया था. इससे, एंट्री के स्नैपशॉट का कलेक्शन दिखता है. इसका इस्तेमाल, उदाहरण के लिए, किसी पेज पर उपयोगकर्ता के नेविगेट करने के तरीके के आधार पर अलग यूज़र इंटरफ़ेस दिखाने के लिए किया जा सकता है. इसके अलावा, इसका इस्तेमाल पिछले यूआरएल या उनकी स्थितियों को देखने के लिए भी किया जा सकता है. मौजूदा इतिहास एपीआई की मदद से ऐसा नहीं किया जा सकता.

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

बाईं या दाईं ओर से खोलने का डेमो

navigation में नेविगेट करने के कई अन्य तरीके भी हैं. ये सभी तरीके, { committed, finished } वाला ऑब्जेक्ट दिखाते हैं. मैंने traverseTo() और navigate() के बारे में पहले ही बता दिया है. यह key को स्वीकार करता है, जो उपयोगकर्ता के इतिहास में किसी खास एंट्री को दिखाता है. इसमें 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" इवेंट लिसनर को एक ही जगह से कंट्रोल किया जाता है. इसके बावजूद, मौजूदा नेविगेशन एपीआई स्पेसिफ़िकेशन, पेज के पहली बार लोड होने पर "navigate" को ट्रिगर नहीं करता. जिन साइटों के लिए सभी स्टेटस में सर्वर साइड रेंडरिंग (एसएसआर) का इस्तेमाल किया जाता है उनके लिए यह ठीक हो सकता है—आपका सर्वर सही शुरुआती स्टेटस दिखा सकता है. यह आपके उपयोगकर्ताओं को कॉन्टेंट दिखाने का सबसे तेज़ तरीका है. हालांकि, जो साइटें अपने पेज बनाने के लिए क्लाइंट-साइड कोड का इस्तेमाल करती हैं उन्हें अपने पेज को शुरू करने के लिए, एक और फ़ंक्शन बनाना पड़ सकता है.

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

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

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

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

Navigation API आज़माएं

Navigation API, Chrome 102 में बिना फ़्लैग के उपलब्ध है. आपके पास डॉमिनिक डेनिकोला का डेमो देखने का विकल्प भी है.

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

रेफ़रंस

धन्यवाद

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