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ụ: Scroll Restoration (Phục hồi 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 cho 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ý thao tác đ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 thể 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 đang nhấn nút Quay lại hoặc Tiếp theo trong trình duyệt; bạn không được giữ 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 chính hoạt động đ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 bạn 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 đề xuất khó khăn khi sử dụng các API cũ.
Nếu từng viết tuyến đường cho SPA của riêng mình bằng History API, 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à biến mất trên trang của bạn, đồng thời đây không phải là cách duy nhất để người dùng có thể di chuyển 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 vùng giao diện: 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ý thao tác đ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 khác nguồn gốc.
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 điều hướng là 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 giá trị này là true, thì thao tác điều hướng được 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. 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 ghi lại 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
trả về một lời hứa (xảy ra tự động với các hàm không đồng bộ), thì lời hứa đó sẽ cho trình duyệt biết thời gian điều hướng và liệu thao tác điều hướng 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.
Chúng tôi đang thảo luận về cách trì hoãn việc thay đổi URL trên GitHub, nhưng bạn nên cập nhật ngay trang bằng một số 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.
Tín hiệu huỷ
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 việc điều hướng có thể trở nên thừa.
Đ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 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ý mọi khả năng này, sự kiện được truyền đến trình nghe "navigate"
chứa một thuộc tính signal
, là một 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 bất kỳ lệnh gọi nào bạn thực hiện đến fetch()
. Thao tác này sẽ huỷ các yêu cầu mạng đang diễn ra nếu thao tác điều hướng bị chiếm quyền.
Điều này vừa giúp 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 đó thực hiện các thao tác như cập nhật DOM để hiển thị thao tác đ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 thành phần điều hướng, trình duyệt sẽ cố gắng tự động xử lý thao tác cuộn.
Đối với các 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 mảnh 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, việc này xảy ra sau khi lời hứa do handler
trả về được giải quyết, nhưng nếu cầ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 áp 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ằngEvent
. - 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 không thành công theo 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à khe của mục nhập đó.
Khoá này vẫn 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 nút Quay lại rồi mở lại cùng một trang, key
sẽ thay đổi vì mục nhập mới này tạo ra một khe mới.
Đối với nhà phát triển, key
rất hữu ích vì Navigation API cho phép bạn trực tiếp điều hướng người dùng đến một mục có khoá khớp.
Bạn có thể giữ lại trạng thái này, ngay cả ở trạng thái của các mục nhập khác, để dễ dàng chuyển đổi 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.
API này rất giống với history.state
trong API Nhật ký, nhưng được cải tiến.
Trong Navigation API, bạn có thể gọi phương thức .getState()
của mục hiện tại (hoặc bất kỳ mục nào) để trả về bản sao trạng thái của mục đó:
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à bất kỳ đối tượng có thể sao chép nào.
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 ra 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, thay đổi trạng thái đã được áp dụng đầy đủ vào thời điểm mã của bạn nhận được thông tin về thay đổi đó, chẳng hạn như khi người dùng bật/tắt một phần tử <details>
hoặc người dùng thay đổi trạng thái của dữ liệu đầu vào trong 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ộ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 có thể sử dụng trạng thái này cho tất cả trạng thái ứ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 các mục mà người dùng đã điều hướng qua 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 di chuyển ngược 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 bạn bè, 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 phương thức gọi có thể đợi cho đến khi quá trình chuyển đổi "được thực hiện" (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 do intercept({ handler })
trả về đều hoàn tất hoặc bị từ chối do không thành công hoặc bị một thao tác điều hướng khác chiếm quyền).
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ập nhật ký mới, có sẵn thông qua phương thức.getState()
trênNavigationHistoryEntry
.history
: có thể được đặt thành"replace"
để thay thế mục nhập nhật ký hiện tại.info
: một đối tượng để truyền đến sự kiện điều hướng thô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 #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 đã đề 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()
.
Tệp 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, việc gửi <form>
HTML qua POST là một loại thao tác điều hướng đặc biệt và Navigation API có thể chặn thao tác 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 việc gửi biểu mẫu bằng cách tìm thuộc tính formData
trên NavigateEvent
.
Dưới đây là ví dụ đơn giản về việc chuyển mọi lượt gửi biểu mẫu thành một lượt gửi biểu mẫu vẫn nằm 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ó bản chất tập trung, nhưng thông số kỹ thuật hiện tại của Navigation API 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ủ ý khác của Navigation API 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ố tác động thú vị được ghi nhận thêm trong thông số kỹ thuật, nhưng trong thực tế, sẽ giúp nhà phát triển ít nhầm lẫn hơ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 mục mà người dùng đã điều hướng theo phương thức lập trình. Vấn đề này đang được thảo luận, nhưng một lựa chọn có thể là chỉ cho phép xoá: các mục nhập trước đây hoặc "tất cả mục nhập trong tương lai". Lớ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
Đây có thể là lựa chọn hoàn hảo cho các cửa sổ bật lên tạm thời hoặc quảng cáo xen kẽ: URL mới là nội dung mà người dùng có thể sử dụng cử chỉ Quay lại để rời khỏi, nhưng sau đó họ không thể vô tình chuyển sang Tiến để mở lại URL đó (vì mục nhập đã bị xoá). 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ù History API cổ điển có vẻ đơn giản, nhưng API này không được xác định rõ ràng và có rất nhiều vấn đề liên quan đến các trường hợp đặc biệt và cách triển khai API này trên các trình duyệt. Chúng tôi hy vọng bạn cân nhắc việ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.