مسیریابی مدرن سمت مشتری: Navigation API

استاندارد کردن مسیریابی سمت مشتری از طریق یک API کاملاً جدید که به طور کامل برنامه های کاربردی تک صفحه ای را بازسازی می کند.

پشتیبانی مرورگر

  • 102
  • 102
  • ایکس
  • ایکس

منبع

برنامه‌های تک صفحه‌ای یا SPA با یک ویژگی اصلی تعریف می‌شوند: به‌جای روش پیش‌فرض بارگیری صفحات کاملاً جدید از سرور، به‌صورت پویا محتوای خود را در هنگام تعامل کاربر با سایت بازنویسی می‌کنند.

در حالی که SPAها توانسته‌اند این ویژگی را از طریق History API (یا در موارد محدود، با تنظیم بخش #هش سایت) به شما ارائه دهند، این یک API ناهموار است که مدت‌ها قبل از اینکه SPA‌ها معمولی باشند توسعه یافته است – و وب در حال فریاد زدن است. رویکرد کاملا جدید Navigation API یک API پیشنهادی است که به‌جای تلاش برای اصلاح لبه‌های ناهموار History API، این فضا را کاملاً اصلاح می‌کند. (به عنوان مثال، Scroll Restoration به جای تلاش برای اختراع مجدد، API History را وصله کرد.)

این پست API ناوبری را در سطح بالایی توصیف می کند. اگر می‌خواهید پیشنهاد فنی را بخوانید، پیش‌نویس گزارش را در مخزن WICG بررسی کنید .

مثال استفاده

برای استفاده از Navigation API، با افزودن یک شنونده "navigate" در شی navigation جهانی شروع کنید. این رویداد اساساً متمرکز است: برای همه انواع پیمایش‌ها فعال می‌شود، چه کاربر اقدامی را انجام دهد (مانند کلیک کردن روی پیوند، ارسال یک فرم، یا رفتن به عقب و جلو) یا زمانی که پیمایش به صورت برنامه‌ریزی فعال شود (یعنی از طریق سایت شما کد). در بیشتر موارد، به کد شما اجازه می‌دهد تا رفتار پیش‌فرض مرورگر را برای آن عمل لغو کند. برای SPA ها، این احتمالاً به معنای نگه داشتن کاربر در همان صفحه و بارگیری یا تغییر محتوای سایت است.

یک NavigateEvent به شنونده "navigate" ارسال می شود که حاوی اطلاعاتی در مورد ناوبری، مانند URL مقصد است و به شما امکان می دهد در یک مکان متمرکز به ناوبری پاسخ دهید. یک شنونده اصلی "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() ناوبری را مدیریت کنید. و اگر کاربر دکمه‌های Back یا Forward را در مرورگر خود فشار می‌دهد، نمی‌توانید یک پیمایش را از طریق preventDefault() لغو کنید. شما نباید بتوانید کاربران خود را در سایت خود به دام بیندازید. (این مورد در GitHub مورد بحث قرار گرفته است .)

حتی اگر نتوانید خود ناوبری را متوقف یا رهگیری کنید، رویداد "navigate" همچنان فعال می شود. این آموزنده است، بنابراین کد شما می تواند، برای مثال، یک رویداد Analytics را ثبت کند تا نشان دهد یک کاربر سایت شما را ترک می کند.

چرا رویداد دیگری را به پلتفرم اضافه کنید؟

شنونده رویداد "navigate" مدیریت تغییرات URL را در داخل یک SPA متمرکز می کند. این یک پیشنهاد دشوار با استفاده از APIهای قدیمی است. اگر تا به حال مسیریابی را برای SPA خود با استفاده از 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));

این خوب است، اما جامع نیست. پیوندها ممکن است در صفحه شما بیایند و بروند، و آنها تنها راهی نیستند که کاربران بتوانند در صفحات پیمایش کنند. به عنوان مثال، آنها ممکن است یک فرم ارسال کنند یا حتی از یک نقشه تصویری استفاده کنند. صفحه شما ممکن است با این موارد سر و کار داشته باشد، اما یک دنباله طولانی از احتمالات وجود دارد که می‌توان آن‌ها را ساده‌تر کرد – چیزی که API جدید ناوبری به آن دست می‌یابد.

علاوه بر این، موارد فوق ناوبری به عقب/ جلو را مدیریت نمی کند. رویداد دیگری برای آن وجود دارد، "popstate" .

شخصاً، History API اغلب احساس می‌کند که می‌تواند راهی برای کمک به این احتمالات باشد. با این حال، واقعاً فقط دو ناحیه سطحی دارد: پاسخ دادن در صورتی که کاربر در مرورگر خود Back یا Forward را فشار دهد، به علاوه فشار دادن و جایگزین کردن URL ها. برای "navigate" قیاسی ندارد، مگر اینکه شنونده‌ها را به‌طور دستی برای رویدادهای کلیک تنظیم کنید، همانطور که در بالا نشان داده شد.

تصمیم گیری در مورد نحوه مدیریت یک ناوبری

navigateEvent حاوی اطلاعات زیادی در مورد مسیریابی است که می توانید از آنها برای تصمیم گیری در مورد نحوه برخورد با یک ناوبری خاص استفاده کنید.

خواص کلیدی عبارتند از:

canIntercept
اگر این نادرست است، نمی توانید ناوبری را رهگیری کنید. ناوبری های متقاطع و پیمایش اسناد متقابل را نمی توان رهگیری کرد.
destination.url
احتمالاً مهم ترین اطلاعاتی است که باید در هنگام ناوبری در نظر بگیرید.
hashChange
درست است اگر پیمایش همان سند باشد، و هش تنها بخشی از URL است که با URL فعلی متفاوت است. در SPA های مدرن، هش باید برای پیوند دادن به بخش های مختلف سند جاری باشد. بنابراین، اگر 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
  );
}

رهگیری

وقتی کد شما intercept({ handler }) از درون شنونده "navigate" خود فرا می خواند، به مرورگر اطلاع می دهد که اکنون صفحه را برای وضعیت جدید و به روز شده آماده می کند و پیمایش ممکن است کمی طول بکشد.

مرورگر با گرفتن موقعیت اسکرول برای وضعیت فعلی شروع می‌کند، بنابراین می‌توان آن را بعداً به‌صورت اختیاری بازیابی کرد، سپس با 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);
      },
    });
  }
});

به این ترتیب، این API یک مفهوم معنایی را معرفی می‌کند که مرورگر آن را درک می‌کند: در حال حاضر یک ناوبری SPA در حال انجام است، با گذشت زمان، سند را از یک URL قبلی و حالت به یک آدرس جدید تغییر می‌دهد. این چندین مزیت بالقوه دارد، از جمله دسترسی: مرورگرها می توانند شروع، پایان یا شکست احتمالی یک ناوبری را نشان دهند. برای مثال کروم نشانگر بارگیری بومی خود را فعال می کند و به کاربر اجازه می دهد تا با دکمه توقف تعامل داشته باشد. (در حال حاضر وقتی کاربر از طریق دکمه‌های عقب/ جلو حرکت می‌کند، این اتفاق نمی‌افتد، اما به زودی برطرف خواهد شد .)

هنگام رهگیری پیمایش ها، URL جدید درست قبل از فراخوانی تماس handler شما اعمال می شود. اگر بلافاصله DOM را به روز نکنید، دوره ای ایجاد می شود که محتوای قدیمی همراه با URL جدید نمایش داده می شود. این امر بر مواردی مانند وضوح نسبی URL هنگام واکشی داده یا بارگیری منابع فرعی جدید تأثیر می گذارد.

روشی برای به تاخیر انداختن تغییر URL در 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);
      },
    });
  }
});

این نه تنها از مشکلات مربوط به وضوح URL جلوگیری می کند، بلکه به دلیل اینکه شما فوراً به کاربر پاسخ می دهید، سریع نیز احساس می شود.

سیگنال های لغو

از آنجایی که می‌توانید کار ناهمزمان را در یک handler intercept() انجام دهید، این امکان وجود دارد که ناوبری اضافی شود. این زمانی اتفاق می افتد که:

  • کاربر روی پیوند دیگری کلیک می کند، یا کدی پیمایش دیگری را انجام می دهد. در این مورد ناوبری قدیمی به نفع ناوبری جدید کنار گذاشته می شود.
  • کاربر روی دکمه "توقف" در مرورگر کلیک می کند.

برای مقابله با هر یک از این احتمالات، رویداد ارسال شده به شنونده "navigate" حاوی یک ویژگی signal است که یک AbortSignal است. برای اطلاعات بیشتر به واکشی قابل سقط مراجعه کنید.

نسخه کوتاه این است که اساساً یک شی را ارائه می دهد که زمانی که شما باید کار خود را متوقف کنید یک رویداد را روشن می کند. قابل ذکر است، می‌توانید یک AbortSignal را به هر تماسی که برای fetch() انجام می‌دهید، ارسال کنید، که در صورت استفاده از ناوبری، درخواست‌های شبکه در پرواز را لغو می‌کند. این کار هم پهنای باند کاربر را ذخیره می‌کند و هم Promise که توسط fetch() بازگردانده شده است را رد می‌کند و از هرگونه کد زیر از اقداماتی مانند به‌روزرسانی 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" است)، این به معنای تلاش برای پیمایش به قسمتی است که با قطعه URL نشان داده شده است (بیت بعد از # )، یا بازنشانی پیمایش به بالا از صفحه

برای بارگیری مجدد و پیمایش، این به معنای بازیابی موقعیت اسکرول به جایی است که آخرین باری که این ورودی تاریخ نمایش داده شده بود.

به‌طور پیش‌فرض، این اتفاق زمانی رخ می‌دهد که وعده بازگردانده شده توسط 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);
      },
    });
  }
});

همچنین، می‌توانید با تنظیم گزینه scroll intercept() روی "manual" از کنترل خودکار اسکرول به طور کامل انصراف دهید:

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

کنترل تمرکز

هنگامی که وعده بازگردانده شده توسط handler شما برطرف شد، مرورگر اولین عنصر را با مجموعه ویژگی autofocus یا عنصر <body> را در صورتی که هیچ عنصری آن ویژگی را نداشته باشد، متمرکز می کند.

می‌توانید با تنظیم گزینه focusReset از intercept() روی "manual" از این رفتار انصراف دهید:

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

رویدادهای موفقیت و شکست

هنگامی که کنترل کننده intercept() فراخوانی می شود، یکی از این دو اتفاق می افتد:

  • اگر Promise برگشتی محقق شود (یا شما intercept() را فراخوانی نکردید)، Navigation API "navigatesuccess" را با یک Event اجرا می کند.
  • اگر Promise برگشتی رد شود، API "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}`);
});

شنونده رویداد "navigateerror" ، که یک ErrorEvent دریافت می‌کند، بسیار مفید است زیرا تضمین می‌شود هر گونه خطا را از کد شما که صفحه جدیدی را تنظیم می‌کند دریافت کند. شما به سادگی می توانید await fetch() و بدانید که اگر شبکه در دسترس نباشد، خطا در نهایت به "navigateerror" هدایت می شود.

navigation.currentEntry دسترسی به ورودی فعلی را فراهم می کند. این یک شی است که موقعیت کاربر را در حال حاضر توصیف می کند. این ورودی شامل نشانی وب فعلی، ابرداده‌هایی است که می‌توان برای شناسایی این ورودی در طول زمان و وضعیت ارائه‌شده توسط توسعه‌دهنده استفاده کرد.

فراداده شامل key است، یک ویژگی رشته منحصر به فرد هر ورودی که نشان دهنده ورودی فعلی و شکاف آن است. حتی اگر URL ورودی فعلی یا وضعیت تغییر کند، این کلید ثابت می ماند. هنوز در همان اسلات است. برعکس، اگر کاربر Back را فشار دهد و سپس همان صفحه را دوباره باز کند، 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.state در History API است، اما بهبود یافته است.

در 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}) به شما اجازه می دهد آن را در یک مکان مدیریت کنید.

حالت در مقابل پارامترهای URL

از آنجایی که state می تواند یک شی ساخت یافته باشد، استفاده از آن برای تمام حالت های برنامه وسوسه انگیز است. با این حال، در بسیاری از موارد بهتر است آن حالت در URL ذخیره شود.

اگر انتظار دارید زمانی که کاربر URL را با کاربر دیگری به اشتراک می گذارد، وضعیت حفظ شود، آن را در URL ذخیره کنید. در غیر این صورت، شی state گزینه بهتری است.

دسترسی به تمام ورودی ها

هر چند "ورود فعلی" همه چیز نیست. API همچنین راهی برای دسترسی به کل فهرست ورودی‌هایی که کاربر در حین استفاده از سایت شما از طریق فراخوانی navigation.entries() آن پیمایش کرده است، فراهم می‌کند، که آرایه عکس فوری از ورودی‌ها را برمی‌گرداند. این را می توان برای نشان دادن یک رابط کاربری متفاوت بر اساس نحوه پیمایش کاربر به یک صفحه خاص یا فقط برای نگاه کردن به URL های قبلی یا وضعیت آنها استفاده کرد. این کار با History API فعلی غیرممکن است.

همچنین می‌توانید رویداد "dispose" را در NavigationHistoryEntry جداگانه گوش دهید، که وقتی ورودی دیگر بخشی از تاریخچه مرورگر نیست، فعال می‌شود. این می تواند به عنوان بخشی از پاکسازی عمومی اتفاق بیفتد، اما در هنگام ناوبری نیز اتفاق می افتد. به عنوان مثال، اگر 10 مکان را به عقب طی کنید، سپس به جلو بروید، آن 10 ورودی تاریخ حذف خواهند شد.

مثال ها

همانطور که در بالا ذکر شد، رویداد "navigate" برای همه انواع ناوبری فعال می شود. (در واقع یک ضمیمه طولانی در مشخصات همه انواع ممکن وجود دارد.)

در حالی که برای بسیاری از سایت‌ها رایج‌ترین مورد زمانی است که کاربر روی <a href="..."> کلیک می‌کند، دو نوع پیمایش قابل توجه و پیچیده‌تر وجود دارد که ارزش پوشش دادن دارند.

ناوبری برنامه ای

اول ناوبری برنامه‌ای است، که در آن ناوبری توسط فراخوانی روش در کد سمت مشتری شما ایجاد می‌شود.

می‌توانید با navigation.navigate('/another_page') از هر کجای کد خود تماس بگیرید تا پیمایش ایجاد کنید. این کار توسط شنونده رویداد متمرکز ثبت شده در شنونده "navigate" انجام می شود و شنونده متمرکز شما به صورت همزمان فراخوانی می شود.

این به عنوان یک تجمیع بهبود یافته از روش‌های قدیمی‌تر مانند location.assign() و دوستان، به‌علاوه متدهای pushState() و replaceState() History API در نظر گرفته شده است.

متد navigation.navigate() یک شی را برمی‌گرداند که شامل دو نمونه Promise در { committed, finished } است. این به فراخوان‌کننده اجازه می‌دهد تا زمانی که انتقال "متعهد شود" (نشانی اینترنتی قابل مشاهده تغییر کرده و یک NavigationHistoryEntry در دسترس است) یا "تمام" (تمام وعده‌های بازگردانده شده توسط intercept({ handler }) کامل شود یا رد شود، صبر کند. شکست یا پیشی گرفتن توسط ناوبری دیگر).

متد navigate یک شی گزینه نیز دارد که می توانید در آن تنظیم کنید:

  • state : وضعیت ورودی تاریخچه جدید که از طریق متد .getState() در NavigationHistoryEntry موجود است.
  • history : که می تواند روی "replace" تنظیم شود تا جایگزین ورودی تاریخ فعلی شود.
  • info : یک شی برای ارسال به رویداد navigate از طریق navigateEvent.info .

به طور خاص، info می‌تواند برای مثال برای نشان دادن یک انیمیشن خاص که باعث می‌شود صفحه بعدی ظاهر شود مفید باشد. (جایگزین ممکن است تنظیم یک متغیر سراسری یا گنجاندن آن به عنوان بخشی از #hash باشد. هر دو گزینه کمی ناخوشایند هستند.) قابل info ، اگر کاربر بعداً باعث ناوبری شود، به عنوان مثال، از طریق Back و دکمه های جلو. در واقع، همیشه در آن موارد undefined خواهد بود.

دمو باز کردن از چپ یا راست

navigation تعدادی روش ناوبری دیگر نیز دارد که همگی یک شی حاوی { committed, finished } را برمی گرداند. من قبلاً به traverseTo() (که key را می پذیرد که یک ورودی خاص در تاریخچه کاربر را نشان می دهد) و navigate() اشاره کرده ام. همچنین شامل back() ، forward() و reload() می باشد. همه این متدها - درست مانند navigate() - توسط شنونده رویداد متمرکز "navigate" مدیریت می شوند.

فرم های ارسالی

ثانیا، ارسال <form> HTML از طریق POST نوع خاصی از پیمایش است و Navigation API می تواند آن را رهگیری کند. در حالی که شامل یک بار اضافی است، ناوبری همچنان توسط شنونده "navigate" به صورت مرکزی اداره می شود.

ارسال فرم را می توان با جستجوی ویژگی formData در NavigateEvent شناسایی کرد. در اینجا یک مثال آورده شده است که به سادگی هر فرم ارسالی را به فرمی تبدیل می کند که از طریق 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" در اولین بارگذاری صفحه فعال نمی کند. و برای سایت‌هایی که از رندر سمت سرور (SSR) برای همه حالت‌ها استفاده می‌کنند، این ممکن است خوب باشد - سرور شما می‌تواند حالت اولیه صحیح را برگرداند، که سریع‌ترین راه برای دریافت محتوا به کاربران شما است. اما سایت هایی که از کد سمت مشتری برای ایجاد صفحات خود استفاده می کنند، ممکن است نیاز به ایجاد یک تابع اضافی برای مقداردهی اولیه صفحه خود داشته باشند.

یکی دیگر از انتخاب های طراحی عمدی Navigation API این است که فقط در یک فریم - یعنی صفحه سطح بالا یا یک <iframe> خاص عمل می کند. این تعدادی پیامدهای جالب دارد که بیشتر در مشخصات مستند شده است، اما در عمل، سردرگمی توسعه دهندگان را کاهش می دهد. History API قبلی دارای تعدادی لبه گیج کننده است، مانند پشتیبانی از فریم ها، و Navigation API بازسازی شده از همان ابتدا، این موارد لبه را مدیریت می کند.

در نهایت، هنوز اتفاق نظری در مورد تغییر برنامه‌ای یا تنظیم مجدد فهرست ورودی‌هایی که کاربر در آنها پیمایش کرده است، وجود ندارد. این در حال حاضر در حال بحث است، اما یک گزینه می تواند اجازه دادن به حذف فقط باشد: یا ورودی های تاریخی یا "همه ورودی های آینده". دومی به حالت موقت اجازه می دهد. به عنوان مثال، به عنوان یک توسعه دهنده، من می توانستم:

  • با رفتن به URL یا وضعیت جدید از کاربر سوال بپرسید
  • به کاربر اجازه دهید کار خود را کامل کند (یا به عقب برگردد)
  • پس از اتمام یک کار، یک ورودی تاریخچه را حذف کنید

این می تواند برای مدال های موقت یا بینابینی عالی باشد: URL جدید چیزی است که کاربر می تواند از ژست بازگشت برای خروج از آن استفاده کند، اما پس از آن نمی تواند به طور تصادفی به جلو برود تا دوباره آن را باز کند (زیرا ورودی حذف شده است). این فقط با History API فعلی امکان پذیر نیست.

Navigation API را امتحان کنید

Navigation API در Chrome 102 بدون پرچم در دسترس است. همچنین می توانید نسخه ی نمایشی Domenic Denicola را امتحان کنید .

در حالی که API کلاسیک History ساده به نظر می رسد، خیلی خوب تعریف نشده است و دارای تعداد زیادی مشکلات در مورد گوشه ها و نحوه اجرای متفاوت آن در مرورگرها است. امیدواریم بازخورد خود را درباره Navigation API جدید در نظر بگیرید.

منابع

سپاسگزاریها

با تشکر از توماس اشتاینر ، دومنیک دنیکولا و نیت چاپین برای بررسی این پست. تصویر قهرمان از Unsplash , توسط جرمی زیرو .