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

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

सैम थोरोगुड
सैम थॉरोगुड
जेक आर्चिबाल्ड
जेक आर्किबाल्ड

ब्राउज़र सहायता

  • 102
  • 102
  • x
  • x

सोर्स

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

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

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

इस्तेमाल का उदाहरण

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

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

नेविगेशन मैनेज करना

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

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

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

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

यूआरएल में बदलाव में देरी का एक तरीका 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() को कॉल नहीं किया है), तो नेविगेशन एपीआई "navigatesuccess" को Event के साथ सक्रिय करेगा.
  • अगर दिखाया गया Promise अस्वीकार करता है, तो एपीआई "navigateerror" को ErrorEvent के साथ चालू करेगा.

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

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

या सफल न होने पर आप गड़बड़ी का मैसेज दिखा सकते हैं:

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

ErrorEvent को पाने वाले "navigateerror" इवेंट की पहचान करने वाला टूल, खास तौर पर इस्तेमाल में आसान होता है. इसकी मदद से, नया पेज सेट अप करने वाले कोड से गड़बड़ी की जानकारी मिल सकती है. 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;

स्थिति

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

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

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

उदाहरण

"navigate" इवेंट, सभी तरह के नेविगेशन के लिए चालू होता है, जैसा कि ऊपर बताया गया है. (असल में, सभी संभावित टाइप की खास जानकारी में एक लंबा अपेंडिक्स मौजूद है.)

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

प्रोग्रामैटिक नेविगेशन

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

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

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

नेविगेशन एपीआई का एक और जान-बूझकर किया गया डिज़ाइन यह है कि यह सिर्फ़ एक फ़्रेम में काम करता है—यानी, सबसे ऊपर के लेवल का पेज या कोई एक खास <iframe>. इसके कई दिलचस्प नतीजे हैं जिन्हें खास जानकारी में आगे बताया गया है, लेकिन व्यावहारिक तौर पर इससे डेवलपर को लेकर भ्रम कम हो जाएगा. पिछले इतिहास API में कई गुमराह करने वाले केस हैं, जैसे कि फ़्रेम के लिए सहायता. नए तरीके से बनाया गया नेविगेशन एपीआई इन एज केस को कभी भी, कहीं से भी हैंडल करता है.

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

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

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

नेविगेशन एपीआई आज़माएं

नेविगेशन एपीआई, Chrome 102 में बिना फ़्लैग के उपलब्ध है. आप डोमेनिक डेनिसोला का डेमो भी आज़माएं.

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

References

स्वीकार हैं

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