Tiêu chuẩn hoá việc định tuyến phía máy khách thông qua một API hoàn toàn mới, giúp cải tiến toàn bộ việc xây dựng ứng dụng trang đơn.
Ứng dụng trang đơn (SPA) được xác định bằng một tính năng cốt lõi: tự động ghi lại nội dung khi người dùng tương tác với trang web, thay vì phương thức mặc định là tải các trang hoàn toàn mới từ máy chủ.
Mặc dù SPA có thể mang đến cho bạn tính năng này thông qua API Nhật ký (hoặc trong một số ít trường hợp, bằng cách điều chỉnh phần #hash của trang web), nhưng đây là một API cồng kềnh được phát triển từ lâu trước khi SPA trở thành tiêu chuẩn — và web đang cần một phương pháp hoàn toàn mới. Navigation API là một API được đề xuất để hoàn toàn cải tiến không gian này, thay vì chỉ cố gắng vá các điểm yếu của History API. (Ví dụ: Khôi phục cuộn đã vá API Nhật ký thay vì cố gắng tạo lại API này.)
Bài đăng này mô tả Navigation API ở cấp độ tổng quát. Để đọc đề xuất kỹ thuật, hãy xem Báo cáo nháp trong kho lưu trữ WICG.
Ví dụ về cách sử dụng
Để sử dụng Navigation API, hãy bắt đầu bằng cách thêm trình nghe "navigate"
trên đối tượng navigation
toàn cục.
Về cơ bản, sự kiện này là tập trung: sự kiện này sẽ kích hoạt cho tất cả các loại thao tác điều hướng, cho dù người dùng đã thực hiện một hành động (chẳng hạn như nhấp vào một đường liên kết, gửi biểu mẫu hoặc quay lại và chuyển tiếp) hay khi thao tác điều hướng được kích hoạt theo phương thức lập trình (tức là thông qua mã của trang web).
Trong hầu hết các trường hợp, thuộc tính này cho phép mã của bạn ghi đè hành vi mặc định của trình duyệt đối với hành động đó.
Đối với SPA, điều đó có thể có nghĩa là giữ người dùng ở cùng một trang và tải hoặc thay đổi nội dung của trang web.
NavigateEvent
được truyền đến trình nghe "navigate"
chứa thông tin về thao tác điều hướng, chẳng hạn như URL đích đến và cho phép bạn phản hồi thao tác điều hướng ở một nơi tập trung.
Trình nghe "navigate"
cơ bản có thể có dạng như sau:
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});
}
});
Bạn có thể xử lý việc điều hướng theo một trong hai cách sau:
- Gọi
intercept({ handler })
(như mô tả ở trên) để xử lý hoạt động điều hướng. - Gọi
preventDefault()
, thao tác này có thể huỷ hoàn toàn thao tác điều hướng.
Ví dụ này gọi intercept()
trên sự kiện.
Trình duyệt sẽ gọi lệnh gọi lại handler
để định cấu hình trạng thái tiếp theo của trang web.
Thao tác này sẽ tạo một đối tượng chuyển đổi, navigation.transition
, mà mã khác có thể sử dụng để theo dõi tiến trình điều hướng.
Cả intercept()
và preventDefault()
thường được cho phép, nhưng có một số trường hợp không gọi được.
Bạn không thể xử lý các thao tác điều hướng thông qua intercept()
nếu thao tác điều hướng đó là thao tác điều hướng trên nhiều nguồn gốc.
Ngoài ra, bạn không thể huỷ thao tác điều hướng thông qua preventDefault()
nếu người dùng nhấn nút Quay lại hoặc Tiến trong trình duyệt; do đó, bạn không thể chặn người dùng trên trang web của mình.
(Vấn đề này đang được thảo luận trên GitHub.)
Ngay cả khi bạn không thể dừng hoặc chặn quá trình điều hướng, sự kiện "navigate"
vẫn sẽ kích hoạt.
Đây là một sự kiện cung cấp thông tin, vì vậy, mã của bạn có thể ghi lại một sự kiện Analytics để cho biết rằng người dùng đang rời khỏi trang web của bạn.
Tại sao nên thêm một sự kiện khác vào nền tảng?
Trình nghe sự kiện "navigate"
tập trung xử lý các thay đổi về URL bên trong một SPA.
Đây là một tuyên bố khó khăn khi sử dụng các API cũ.
Nếu bạn đã từng viết định tuyến cho SPA của riêng mình bằng cách sử dụng API Lịch sử, thì bạn có thể đã thêm mã như sau:
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));
Điều này là bình thường, nhưng chưa đầy đủ. Đường liên kết có thể xuất hiện và xuất hiện trên trang của bạn, đây không phải là cách duy nhất để người dùng điều hướng qua các trang. Ví dụ: họ có thể gửi một biểu mẫu hoặc thậm chí sử dụng bản đồ hình ảnh. Trang của bạn có thể xử lý những vấn đề này, nhưng có rất nhiều khả năng có thể được đơn giản hoá – điều mà Navigation API mới đạt được.
Ngoài ra, mã trên không xử lý thao tác điều hướng lui/tiến. Có một sự kiện khác cho việc đó, "popstate"
.
Theo cá nhân tôi, History API thường có vẻ như có thể giúp ích cho những khả năng này.
Tuy nhiên, thực sự thì lớp này chỉ có hai khu vực bề mặt: phản hồi nếu người dùng nhấn nút Quay lại hoặc Tiếp tục trong trình duyệt, cùng với việc đẩy và thay thế URL. Lớp này không tương tự như "navigate"
, ngoại trừ trường hợp bạn thiết lập trình nghe theo cách thủ công cho các sự kiện nhấp, chẳng hạn như minh hoạ ở trên.
Quyết định cách xử lý hoạt động điều hướng
navigateEvent
chứa nhiều thông tin về thao tác điều hướng mà bạn có thể sử dụng để quyết định cách xử lý một thao tác điều hướng cụ thể.
Các thuộc tính chính là:
canIntercept
- Nếu giá trị này là sai, bạn không thể chặn thao tác điều hướng. Không thể chặn các thao tác điều hướng và di chuyển giữa các tài liệu.
destination.url
- Có lẽ đây là thông tin quan trọng nhất cần cân nhắc khi xử lý thao tác điều hướng.
hashChange
- Đúng nếu phần điều hướng sử dụng cùng một tài liệu và hàm băm là phần duy nhất của URL khác với URL hiện tại.
Trong các SPA hiện đại, hàm băm phải dùng để liên kết đến các phần khác nhau của tài liệu hiện tại. Vì vậy, nếu
hashChange
là true, có thể bạn không cần chặn thao tác điều hướng này. downloadRequest
- Nếu đúng như vậy, thì quá trình điều hướng sẽ bắt đầu bằng một đường liên kết có thuộc tính
download
. Trong hầu hết các trường hợp, bạn không cần chặn yêu cầu này. formData
- Nếu không phải là giá trị rỗng, thì thao tác điều hướng này là một phần của quá trình gửi biểu mẫu POST.
Hãy nhớ tính đến điều này khi xử lý thao tác điều hướng.
Nếu bạn chỉ muốn xử lý các thao tác điều hướng GET, hãy tránh chặn các thao tác điều hướng mà
formData
không phải là giá trị rỗng. Hãy xem ví dụ về cách xử lý lượt gửi biểu mẫu ở phần sau của bài viết này. navigationType
- Đây là một trong các giá trị
"reload"
,"push"
,"replace"
hoặc"traverse"
. Nếu là"traverse"
, thì bạn không thể huỷ thao tác điều hướng này thông quapreventDefault()
.
Ví dụ: hàm shouldNotIntercept
dùng trong ví dụ đầu tiên có thể có dạng như sau:
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
);
}
Chặn
Khi mã gọi intercept({ handler })
từ trình nghe "navigate"
, mã sẽ thông báo cho trình duyệt rằng mã đang chuẩn bị trang cho trạng thái mới, đã cập nhật và việc điều hướng có thể mất chút thời gian.
Trình duyệt bắt đầu bằng cách chụp vị trí cuộn cho trạng thái hiện tại, vì vậy, bạn có thể khôi phục vị trí này sau (không bắt buộc), sau đó gọi lệnh gọi lại handler
.
Nếu handler
của bạn trả về một lời hứa (tự động diễn ra với các hàm không đồng bộ), thì lời hứa đó sẽ cho trình duyệt biết quá trình điều hướng mất bao lâu và liệu có thành công hay không.
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);
},
});
}
});
Do đó, API này giới thiệu một khái niệm ngữ nghĩa mà trình duyệt hiểu được: một thao tác điều hướng SPA đang diễn ra, theo thời gian, thay đổi tài liệu từ URL và trạng thái trước đó thành một URL và trạng thái mới. Điều này có một số lợi ích tiềm năng, bao gồm cả khả năng hỗ trợ tiếp cận: trình duyệt có thể hiển thị phần bắt đầu, kết thúc hoặc lỗi tiềm ẩn của một thao tác điều hướng. Ví dụ: Chrome kích hoạt chỉ báo tải gốc và cho phép người dùng tương tác với nút dừng. (Điều này hiện không xảy ra khi người dùng điều hướng qua các nút quay lại/tiến, nhưng vấn đề này sẽ sớm được khắc phục.)
Cam kết điều hướng
Khi chặn thao tác điều hướng, URL mới sẽ có hiệu lực ngay trước khi lệnh gọi lại handler
được gọi.
Nếu bạn không cập nhật DOM ngay lập tức, thì điều này sẽ tạo ra một khoảng thời gian mà nội dung cũ sẽ hiển thị cùng với URL mới.
Điều này ảnh hưởng đến các vấn đề như độ phân giải URL tương đối khi tìm nạp dữ liệu hoặc tải tài nguyên phụ mới.
Để trì hoãn việc thay đổi URL, chúng tôi sẽ thảo luận trên GitHub, nhưng bạn nên cập nhật trang ngay lập tức bằng một số loại phần giữ chỗ cho nội dung sắp tới:
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);
},
});
}
});
Điều này không chỉ tránh được các vấn đề về độ phân giải URL mà còn mang lại cảm giác nhanh chóng vì bạn phản hồi người dùng ngay lập tức.
Huỷ các tín hiệu
Vì bạn có thể thực hiện công việc không đồng bộ trong trình xử lý intercept()
, nên thành phần điều hướng có thể trở thành không cần thiết.
Điều này xảy ra khi:
- Người dùng nhấp vào một đường liên kết khác hoặc một số mã thực hiện một thao tác điều hướng khác. Trong trường hợp này, thành phần điều hướng cũ sẽ bị bỏ qua để chuyển sang thành phần điều hướng mới.
- Người dùng nhấp vào nút "dừng" trong trình duyệt.
Để xử lý bất kỳ khả năng nào trong số này, sự kiện được truyền đến trình nghe "navigate"
chứa một thuộc tính signal
, đó là AbortSignal
.
Để biết thêm thông tin, hãy xem phần Tìm nạp có thể huỷ.
Tóm lại, về cơ bản, lớp này cung cấp một đối tượng kích hoạt một sự kiện khi bạn nên dừng công việc.
Đáng chú ý là bạn có thể truyền AbortSignal
đến mọi lệnh gọi mà bạn thực hiện đến fetch()
. Thao tác này sẽ huỷ các yêu cầu mạng đang bay nếu quá trình điều hướng bị giành quyền.
Thao tác này sẽ vừa tiết kiệm băng thông của người dùng, vừa từ chối Promise
do fetch()
trả về, ngăn mọi mã sau đây thực hiện các hành động như cập nhật DOM để hiển thị thành phần điều hướng trang hiện không hợp lệ.
Dưới đây là ví dụ trước, nhưng với getArticleContent
cùng dòng, cho thấy cách sử dụng AbortSignal
với 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);
},
});
}
});
Xử lý thao tác cuộn
Khi bạn intercept()
một mục điều hướng, trình duyệt sẽ tìm cách xử lý thao tác tự động cuộn.
Đối với thao tác điều hướng đến một mục nhật ký mới (khi navigationEvent.navigationType
là "push"
hoặc "replace"
), điều này có nghĩa là cố gắng cuộn đến phần được chỉ định bằng phân đoạn URL (bit sau #
) hoặc đặt lại thao tác cuộn về đầu trang.
Đối với các lượt tải lại và di chuyển, điều này có nghĩa là khôi phục vị trí cuộn về vị trí của lần gần đây nhất mục nhật ký này xuất hiện.
Theo mặc định, điều này xảy ra sau khi lời hứa mà handler
trả về được phân giải, nhưng nếu bạn nên cuộn sớm hơn, bạn có thể gọi 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);
},
});
}
});
Ngoài ra, bạn có thể chọn không sử dụng tính năng tự động xử lý thao tác cuộn bằng cách đặt tuỳ chọn scroll
của intercept()
thành "manual"
:
navigateEvent.intercept({
scroll: 'manual',
async handler() {
// …
},
});
Xử lý tiêu điểm
Sau khi lời hứa do handler
trả về được giải quyết, trình duyệt sẽ đặt tiêu điểm vào phần tử đầu tiên có thuộc tính autofocus
hoặc phần tử <body>
nếu không có phần tử nào có thuộc tính đó.
Bạn có thể chọn không sử dụng hành vi này bằng cách đặt tuỳ chọn focusReset
của intercept()
thành "manual"
:
navigateEvent.intercept({
focusReset: 'manual',
async handler() {
// …
},
});
Sự kiện thành công và không thành công
Khi trình xử lý intercept()
được gọi, một trong hai điều sau sẽ xảy ra:
- Nếu
Promise
được trả về đáp ứng (hoặc bạn không gọiintercept()
), thì Navigation API sẽ kích hoạt"navigatesuccess"
bằng mộtEvent
. - Nếu
Promise
được trả về từ chối, API sẽ kích hoạt"navigateerror"
bằngErrorEvent
.
Các sự kiện này cho phép mã của bạn xử lý thành công hoặc thất bại một cách tập trung. Ví dụ: bạn có thể xử lý thành công bằng cách ẩn chỉ báo tiến trình đã hiển thị trước đó như sau:
navigation.addEventListener('navigatesuccess', event => {
loadingIndicator.hidden = true;
});
Hoặc bạn có thể hiển thị thông báo lỗi khi không thành công:
navigation.addEventListener('navigateerror', event => {
loadingIndicator.hidden = true; // also hide indicator
showMessage(`Failed to load page: ${event.message}`);
});
Trình nghe sự kiện "navigateerror"
(nhận ErrorEvent
) đặc biệt hữu ích vì đảm bảo nhận được mọi lỗi từ mã đang thiết lập trang mới.
Bạn chỉ cần await fetch()
biết rằng nếu không có mạng, lỗi cuối cùng sẽ được chuyển đến "navigateerror"
.
Mục điều hướng
navigation.currentEntry
cung cấp quyền truy cập vào mục nhập hiện tại.
Đây là đối tượng mô tả vị trí hiện tại của người dùng.
Mục này bao gồm URL hiện tại, siêu dữ liệu có thể dùng để xác định mục này theo thời gian và trạng thái do nhà phát triển cung cấp.
Siêu dữ liệu bao gồm key
, một thuộc tính chuỗi duy nhất của mỗi mục nhập đại diện cho mục nhập hiện tại và ô trống của mục đó.
Khoá này giữ nguyên ngay cả khi URL hoặc trạng thái của mục nhập hiện tại thay đổi.
Thẻ vẫn ở cùng một khe.
Ngược lại, nếu người dùng nhấn vào Quay lại rồi mở lại cùng một trang, thì key
sẽ thay đổi khi mục nhập mới này tạo một khung giờ mới.
Đối với nhà phát triển, key
rất hữu ích vì API điều hướng cho phép bạn chuyển trực tiếp người dùng đến một mục bằng khoá trùng khớp.
Bạn có thể giữ thanh này, ngay cả ở trạng thái của các mục khác, để dễ dàng chuyển giữa các trang.
// 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;
Tiểu bang
Navigation API hiển thị khái niệm "trạng thái". Đây là thông tin do nhà phát triển cung cấp, được lưu trữ liên tục trên mục nhật ký hiện tại nhưng người dùng không thể trực tiếp nhìn thấy.
Tính năng này cực kỳ giống với nhưng có cải tiến từ history.state
trong API Lịch sử.
Trong Navigation API (API Điều hướng), bạn có thể gọi phương thức .getState()
của mục nhập hiện tại (hoặc bất kỳ mục nhập nào) để trả về bản sao trạng thái của mục nhập đó:
console.log(navigation.currentEntry.getState());
Theo mặc định, giá trị này sẽ là undefined
.
Thiết lập trạng thái
Mặc dù các đối tượng trạng thái có thể được thay đổi, nhưng những thay đổi đó sẽ không được lưu lại bằng mục nhập nhật ký, vì vậy:
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
Cách chính xác để đặt trạng thái là trong quá trình điều hướng tập lệnh:
navigation.navigate(url, {state: newState});
// Or:
navigation.reload({state: newState});
Trong đó, newState
có thể là mọi đối tượng có thể sao chép.
Nếu bạn muốn cập nhật trạng thái của mục hiện tại, tốt nhất bạn nên thực hiện thao tác điều hướng thay thế mục hiện tại:
navigation.navigate(location.href, {state: newState, history: 'replace'});
Sau đó, trình nghe sự kiện "navigate"
có thể nhận thay đổi này thông qua navigateEvent.destination
:
navigation.addEventListener('navigate', navigateEvent => {
console.log(navigateEvent.destination.getState());
});
Cập nhật trạng thái đồng bộ
Nhìn chung, bạn nên cập nhật trạng thái không đồng bộ thông qua navigation.reload({state: newState})
, sau đó trình nghe "navigate"
có thể áp dụng trạng thái đó. Tuy nhiên, đôi khi sự thay đổi về trạng thái đã được áp dụng đầy đủ vào thời điểm mã của bạn nghe về sự thay đổi đó, chẳng hạn như khi người dùng bật/tắt phần tử <details>
hoặc người dùng thay đổi trạng thái của phương thức nhập vào biểu mẫu. Trong những trường hợp này, bạn nên cập nhật trạng thái để các thay đổi này được giữ nguyên thông qua các lần tải lại và truy cập. Bạn có thể thực hiện việc này bằng cách sử dụng updateCurrentEntry()
:
navigation.updateCurrentEntry({state: newState});
Chúng tôi cũng sẽ tổ chức một sự kiện để bạn tìm hiểu về thay đổi này:
navigation.addEventListener('currententrychange', () => {
console.log(navigation.currentEntry.getState());
});
Tuy nhiên, nếu thấy mình phản ứng với các thay đổi về trạng thái trong "currententrychange"
, thì có thể bạn đang tách hoặc thậm chí sao chép mã xử lý trạng thái giữa sự kiện "navigate"
và sự kiện "currententrychange"
, trong khi navigation.reload({state: newState})
cho phép bạn xử lý mã ở một nơi.
Tham số trạng thái so với tham số URL
Vì trạng thái có thể là một đối tượng có cấu trúc, nên bạn muốn sử dụng trạng thái này cho tất cả trạng thái của ứng dụng. Tuy nhiên, trong nhiều trường hợp, bạn nên lưu trữ trạng thái đó trong URL.
Nếu bạn muốn trạng thái được giữ nguyên khi người dùng chia sẻ URL với người dùng khác, hãy lưu trữ trạng thái đó trong URL. Nếu không, đối tượng trạng thái sẽ là lựa chọn tốt hơn.
Truy cập vào tất cả các mục
Tuy nhiên, "mục nhập hiện tại" không phải là tất cả.
API này cũng cung cấp một cách để truy cập vào toàn bộ danh sách mục mà người dùng đã truy cập trong khi sử dụng trang web của bạn thông qua lệnh gọi navigation.entries()
. Lệnh gọi này sẽ trả về một mảng tổng quan nhanh về các mục.
Bạn có thể dùng thông tin này để hiển thị một giao diện người dùng khác dựa trên cách người dùng chuyển đến một trang nhất định, hoặc chỉ để xem lại các URL trước đó hoặc trạng thái của các URL đó.
Điều này không thể thực hiện được với API Nhật ký hiện tại.
Bạn cũng có thể theo dõi sự kiện "dispose"
trên từng NavigationHistoryEntry
. Sự kiện này được kích hoạt khi mục nhập không còn nằm trong nhật ký duyệt web. Điều này có thể xảy ra trong quá trình dọn dẹp chung, nhưng cũng có thể xảy ra khi điều hướng. Ví dụ: nếu bạn quay lại 10 vị trí, sau đó di chuyển về phía trước, thì 10 mục nhập nhật ký đó sẽ bị loại bỏ.
Ví dụ
Sự kiện "navigate"
sẽ kích hoạt cho tất cả các loại thao tác điều hướng, như đã đề cập ở trên.
(Thực ra, có một phần phụ lục dài trong thông số kỹ thuật về tất cả các loại có thể có.)
Mặc dù đối với nhiều trang web, trường hợp phổ biến nhất là khi người dùng nhấp vào <a href="...">
, nhưng có hai loại điều hướng phức tạp hơn đáng chú ý.
Điều hướng có lập trình
Thứ nhất là điều hướng có lập trình, trong đó điều hướng là do lệnh gọi phương thức bên trong mã phía máy khách gây ra.
Bạn có thể gọi navigation.navigate('/another_page')
từ bất kỳ vị trí nào trong mã để điều hướng.
Việc này sẽ do trình nghe sự kiện tập trung đã đăng ký trên trình nghe "navigate"
xử lý và trình nghe tập trung của bạn sẽ được gọi đồng bộ.
Đây là một phương thức tổng hợp cải tiến của các phương thức cũ như location.assign()
và các phương thức khác, cùng với các phương thức pushState()
và replaceState()
của API Nhật ký.
Phương thức navigation.navigate()
trả về một đối tượng chứa hai thực thể Promise
trong { committed, finished }
.
Điều này cho phép trình gọi có thể chờ cho đến khi quá trình chuyển đổi được "cam kết" (URL hiển thị đã thay đổi và có NavigationHistoryEntry
mới) hoặc "hoàn tất" (tất cả các lời hứa mà intercept({ handler })
trả về đã hoàn tất hoặc bị từ chối do không thành công hoặc bị giành quyền bởi một thao tác điều hướng khác).
Phương thức navigate
cũng có một đối tượng tuỳ chọn, trong đó bạn có thể đặt:
state
: trạng thái của mục nhật ký mới, có sẵn qua phương thức.getState()
trênNavigationHistoryEntry
.history
: có thể đặt thành"replace"
để thay thế mục nhật ký hiện tại.info
: một đối tượng để truyền đến sự kiện điều hướng quanavigateEvent.info
.
Cụ thể, info
có thể hữu ích để, ví dụ: biểu thị một ảnh động cụ thể khiến trang tiếp theo xuất hiện.
(Cách thay thế có thể là đặt một biến toàn cục hoặc đưa biến đó vào dấu #hash. Cả hai lựa chọn đều hơi khó xử.)
Đáng chú ý là info
này sẽ không được phát lại nếu sau đó người dùng thực hiện thao tác điều hướng, ví dụ: thông qua các nút Quay lại và Tiếp tục.
Trên thực tế, giá trị này sẽ luôn là undefined
trong những trường hợp đó.
navigation
cũng có một số phương thức điều hướng khác, tất cả đều trả về một đối tượng chứa { committed, finished }
.
Tôi từng đề cập đến traverseTo()
(chấp nhận key
biểu thị một mục cụ thể trong nhật ký của người dùng) và navigate()
.
Phiên bản này cũng bao gồm back()
, forward()
và reload()
.
Tất cả các phương thức này đều được xử lý (giống như navigate()
) bằng trình nghe sự kiện "navigate"
tập trung.
Lượt gửi biểu mẫu
Thứ hai, gửi HTML <form>
qua POST là một loại điều hướng đặc biệt và API Điều hướng có thể chặn điều này.
Mặc dù bao gồm một tải trọng bổ sung, nhưng hoạt động điều hướng vẫn do trình nghe "navigate"
xử lý tập trung.
Bạn có thể phát hiện lượt gửi biểu mẫu bằng cách tìm thuộc tính formData
trên NavigateEvent
.
Dưới đây là một ví dụ chỉ cần chuyển mọi lượt gửi biểu mẫu thành một biểu mẫu ở lại trên trang hiện tại thông qua 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"
},
});
}
});
Thông tin nào còn thiếu?
Mặc dù trình nghe sự kiện "navigate"
có tính chất tập trung, nhưng thông số kỹ thuật hiện tại của API Điều hướng không kích hoạt "navigate"
trong lần tải đầu tiên của trang.
Đối với những trang web sử dụng tính năng Kết xuất phía máy chủ (SSR) cho tất cả các trạng thái, thì điều này có thể không gây ra vấn đề gì – máy chủ của bạn có thể trả về trạng thái ban đầu chính xác, đây là cách nhanh nhất để cung cấp nội dung cho người dùng.
Tuy nhiên, các trang web tận dụng mã phía máy khách để tạo trang có thể cần tạo một hàm bổ sung để khởi chạy trang.
Một lựa chọn thiết kế có chủ đích khác của API Điều hướng là API này chỉ hoạt động trong một khung duy nhất, tức là trang cấp cao nhất hoặc một <iframe>
cụ thể.
Điều này có một số ý nghĩa thú vị được nêu thêm trong thông số kỹ thuật, nhưng trên thực tế, điều này sẽ giúp nhà phát triển bớt nhầm lẫn.
API Nhật ký trước đây có một số trường hợp đặc biệt gây nhầm lẫn, chẳng hạn như hỗ trợ khung hình, và API Điều hướng được thiết kế lại xử lý những trường hợp đặc biệt này ngay từ đầu.
Cuối cùng, chưa có sự đồng thuận về việc sửa đổi hoặc sắp xếp lại danh sách các mục mà người dùng đã điều hướng bằng cách lập trình. Nội dung này hiện đang được thảo luận, nhưng có một lựa chọn có thể là chỉ cho phép xoá: có thể là các mục cũ hoặc "tất cả các mục trong tương lai". Trường hợp sau sẽ cho phép trạng thái tạm thời. Ví dụ: với tư cách là nhà phát triển, tôi có thể:
- hỏi người dùng một câu hỏi bằng cách chuyển đến URL hoặc trạng thái mới
- cho phép người dùng hoàn tất công việc (hoặc quay lại)
- xoá một mục trong nhật ký khi hoàn thành việc cần làm
Điều này có thể phù hợp với các phương thức tạm thời hoặc quảng cáo xen kẽ: URL mới là URL mà người dùng có thể sử dụng Cử chỉ quay lại để thoát nhưng sau đó họ không thể vô tình đi tiếp để mở lại URL (vì mục nhập đã bị xóa). Bạn không thể làm việc này với API Nhật ký hiện tại.
Dùng thử Navigation API
Navigation API có trong Chrome 102 mà không cần cờ. Bạn cũng có thể thử một bản minh hoạ của Domenic Denicola.
Mặc dù API Lịch sử cổ điển có vẻ đơn giản, nhưng nó không được định nghĩa rõ ràng và có một số lượng lớn vấn đề liên quan đến các trường hợp sai lệch cũng như cách triển khai khác nhau trên các trình duyệt. Chúng tôi hy vọng bạn cân nhắc đưa ra ý kiến phản hồi về Navigation API mới.
Tài liệu tham khảo
- WICG/navigation-api
- Quan điểm của Mozilla về tiêu chuẩn
- Ý định tạo nguyên mẫu
- Xem xét thẻ
- Mục Chromestatus
Lời cảm ơn
Cảm ơn Thomas Steiner, Domenic Denicola và Nate Chapin đã xem xét bài đăng này.