12/08/2018, 18:25

Single-page app điều khiển history như thế nào ?

Single-page app (SPA) là một website hoạt động dựa việc render lại nội dung trang web theo cử chỉ của người dùng (vd khi người dùng click 1 link) mà không thực hiện request lên server để fetch lại toàn bộ HTML cho trang web đó. Nghe thì có vẻ trìu tượng, và trên thực tế thì có rất nhiều cách để ...

Single-page app (SPA) là một website hoạt động dựa việc render lại nội dung trang web theo cử chỉ của người dùng (vd khi người dùng click 1 link) mà không thực hiện request lên server để fetch lại toàn bộ HTML cho trang web đó. Nghe thì có vẻ trìu tượng, và trên thực tế thì có rất nhiều cách để thực hiện việc implement 1 single-page app, tuy nhiên nhìn chung lại thì hầu hết chúng đều được xây dựng dựa trên những bộ API native và cơ chế của trình duyệt. Hiểu được những điều này chính là cốt lõi để nắm được nguyên tắc hoạt động của 1 single-page app.

Phân loại SPA ?

1 Single-page app có thể quản lý state dựa theo một nguồn bên ngoài (ví dụ địa chỉ URL) hay tự nó có cơ chế quản lý state riêng. So sánh 2 loại với nhau, 1 SPA sử dụng cách thứ hai (internal state) có một hạn chế - đó là nó chỉ có duy nhất 1 đầu vào (entry) - nói cách khác là bạn chỉ có thể truy cập vào app từ 1 địa chỉ cố định (root của trang web). Trong quá trình user navigate trong app, sẽ không có cách nào để thể hiện sự thay đổi đó ra bên ngoài với browser. Điều này dẫn tới một số hạn chế, ví dụ như với use case bạn muốn chia sẻ 1 nội dung nào đó, khi đó người được chia sẻ cũng chỉ có thể truy cập vào từ root, và bạn lại phải mất công giải thích cách truy cập tới nội dung mong muốn.

Với cách tiếp cận thứ nhất - location-based SPA - bạn có thể chia sẻ một đường link cho người khác và có thể chắc chắn rằng bất cứ ai truy cập vào đường link đó cũng sẽ nhìn thấy được một nội dung như nhau (giả sử họ có quyền truy cập để xem được nội dung), do lúc này địa chỉ truy cập luôn tương ứng theo quá trình navigate của người dùng.

Vì vậy, Bài viết này sẽ tập trung vào cơ chế quản lý state dựa trên location.

Location primer

Khi địa chỉ URL là thứ mà người dùng cuối nhìn thấy và tương tác, thì ứng dụng SPA sẽ làm việc với hàm window.location. Hàm này cho phép ta bóc tách và làm việc với từng phần của địa chỉ URL mà không phải tự tay parse.

Đối với một app SPA, chỉ có 3 phần trong địa chỉ URL là quan trọng: pathname, hash và search (hay còn được gọi là query string), còn hostname hay protocol thì có thể bỏ qua.

pathname là phần quan trọng nhất trong 3 phần vì nó quyết định xem nội dụng nào được render. search vàhashđược dùng để hiển thị thêm những data khác. Ví dụ. đối với một địa chỉ như/images?of=mountains, phần pathname/imagessẽ chỉ định rằng trang được render là trang images, trong khi?of=mountains` sẽ quy định thêm nội dung nào được thể hiện trong trang images đó.

Route matching

SPA phụ thuộc vào router. Router là một tập hợp danh sách các route, mỗi một route sẽ tương ứng với một location nó được match tới.

Một route có thể cố định (/about) hoặc có thể chứa thành phần động (/album/:id - với id là giá trị số bất kì).

const routes = [
  { path: '/' },
  { path: '/about' },
  { path: '/album/:id' }
];

Trong quá trình người dùng navigate trong app, location sẽ được so sánh để tìm ra route tương ứng với mình (thường là chỉ so sánh phần pathname của location). Sau khi tìm được route tương ứng, router sẽ thực hiện việc render lại nội dung app cho người dùng (việc render lại có nhiều cách , một trong số đó là phỏng theo observer pattern - LTV sẽ đưa cho router một function có nhiệm vụ render lại trang web, và router sẽ gọi tới function này).

In-App navigation

việc navigation bên trong app cũng có một vấn đề thú vị cần giải quyết. Khi user click vào một đường dẫn ( thường là 1 thẻ <a>), thông thường các browser đều có các behavior mặc định gắn với một event để thực hiện navigate. Với 1 SPA, ta sẽ muốn override lại behavior mặc định này của trình duyệt (có thể sử dụng hàm event.preventDefault() của JS - hay như các framework SPA hiện nay đều đã hỗ trợ mặc định cho vấn đề này). Lúc này, behavior mặc định của trình duyệt sẽ bị ghi đè lên, location không còn tự động thay đổi nữa; thay vào đó quá trình navigate sẽ do ta tự định nghĩa và điều khiển.

Tuy nhiên, có thể bạn sẽ vẫn cần biết một chút về cách mà browser mặc định xử lý với navigation.

Browser xử lý location như thế nào ?

Mỗi một tab browser có một thứ gọi là "browser context". Browser context là thứ quản lý một "session history" - về bản chất là một mảng các location entry.

Một entry này sẽ chứa các thông tin về một location: URL của location, Document tương ứng với location, state đã được serialized, cũng như một vài thuộc tính khác. Mỗi một entry đều có một index đi kèm quy định thứ tự của nó trong mảng session history. Browser context cũng giữ thông tin về entry hiện thời đang được sử dụng.

Document?

Khi trình duyệt thực hiện navigate, một request sẽ được gửi tới server và browser sử dụng response nhận về để tạo một object Document. Object này mô tả trang (cây DOM của trang ...) và các hàm method để tương tác với nó. Ví dụ, hàm window.document chính là hàm để tương tác với Document của location entry hiện tại.

Session history

Môi khi người dùng click vào một link và navigate, tab browser sẽ build thêm vào session history. Mỗi 1 navigate sẽ tạo một request tới server và tạo một entry mới (bao gồm 1 Document)

Khi người dùng ấn nút back trên trình duyệt, browser sẽ sử dụng entry hiện tại để xác định entry mới (current.index -1). Document của entry trước đó sẽ được load lại vào trình duyệt.

Lúc này, khi người dùng click một link, các entry nào nằm phía sau entry hiện tại (những trang trước khi back về) sẽ bị xóa và thay bởi entry mới.

Trong trường hợp người dùng navigate tới đúng trang hiện tại (location mới có cùng pathname, search và hash với location hiện tại), entry hiện tại sẽ bị thay thế mà không ảnh hưởng gì tới các entry khác.

Trên đây là cơ chế hoạt động của navigation, tuy nhiên, mục đích của 1 SPA đó là thực hiện navigate mà không cần phải request tới server. Vậy làm cách nào SPA thực hiện được việc đó ?

History API

Ban đầu, SPA hoạt động dựa trên cơ chế là ta có thể thay đổi hash của location và browser sẽ tạo một location entry mới mà không cần gửi request tới server. Cách này hoạt động, nhưng không được đẹp cho lắm :p Sau này, nguyên một bộ API mới được xây dựng - History API - với mục đích hỗ trợ tận răng cho việc phát triển SPA.

Thay vì việc khởi tạo hẳn một Document với mỗi một location, History API sẽ tái sử dụng lại Document hiện thời, chỉ update nó cho phù hợp với location mới.

History API có 3 function chính: pushState(), replaceState() và go(). 3 hàm này (và các hàm còn lại các của History API) đều có thể được gọi thông qua window.history.

Note: về vấn đề hỗ trợ - tất cả các trình duyệt mới nhất hiện nay đều đã hỗ trợ History API. Các bản IE version nhỏ hơn 9 không hỗ trợ, nhưng ta cũng không việc gì phải quan tâm tới chúng             </div>
            
            <div class=

0