這幾年裡。單頁面應用的架構令人應接不暇,各種新的概念也層出不窮。從過去的 jQuery Mobie、Backbone 到今天的 Angular 2、React、Vue 2,除了版本不同,他們還有非常多的同樣之處。
剛開始寫商業代碼的時候,我使用的是 jQuery。使用 jQuery 來實作功能非常easy,找到一個對應的 jQuery 插件,再編寫對應的功能就可以。
對于單頁面應用亦是如此,尋找一個相輔助的插件就能夠了,如 jQuery Mobile。
雖然在今天看來。jQuery Mobile 已經不适合于今天的多數場景了。這個主要原因是,當時的使用者對于移動 Web 應用的了解和今天是不同的。他們認為移動 Web 應用就是針對移動裝置而訂制的。移動裝置的 UI、更快的載入速度等等。
而在今天,多數的移動 Web 應用,差點兒都是單頁面應用了。
過去。即使我們想建立一個單頁面應用,可能也沒有一個合适的方案。而在今天,可選擇的方案就多了(PS:參見《第四章:學習前端僅僅須要三個月【架構篇】》)。每一個人在不同類型的項目上,也會有不同的方案,沒有一個架構能解決全部的問題
- 對于工作來說。我更希望的是一個完整的解決方式。
- 對于程式設計體驗來說,我喜歡一點點的去創造一些輪子。
當我們會用的架構越多的時候, 所花費的時間抉擇也就越多。而單頁面應用的都有一些同樣的元素。對于這些基本元素的了解,能夠讓我們更快的适合其它架構。
單頁面應用的演進
我接觸到單頁面應用的時候,它看起來就像是将全部的内容放在一個頁面上麼。僅僅須要在一個 HTML 寫好所須要的各個模闆,并在不同的頁面上 data-role 表明這是個頁面(基于 jQuery Mobile)——每一個定義的頁面都和今天的移動應用的模式類似,有 header、content、footer 三件套。
再用 id 來定義好對應的路由。
<div data-role="page" id="foo">
...
</div>
這樣我們就在一個 HTML 裡傳回了全部的頁面了。
随後,僅僅須要在在入口處的 href 裡,寫好對應的 ID 就可以。
當我們點選對應的連結時,就會切換到 HTML 中對應的 ID。這種簡單的單頁面應用基本上就是一個離線應用了。僅僅适合于簡單的場景,但是它帶有單頁面應用的基本特性。而複雜的應用。則須要從server擷取資料。然而早期受限于移動浏覽器性能的影響,僅僅能從server擷取對應的 HTML。并替換目前的頁面。
在這種應用中。我們能夠看到單頁面應用的基本元素: 頁面路由,通過某種方式。如 URL hash 來說明表明目前所在的頁面。并擁有從一個頁面跳轉到另外一個頁面的入口。
當移動裝置的性能越來越好時,開發人員們開始在浏覽器裡渲染頁面:
- 使用 jQuery 來做頁面互動
- 使用 jQuery Ajax 來從服務端擷取資料
- 使用 Backbone 來負責路由及 Model
- 使用 Mustache 作為模闆引擎來渲染頁面
- 使用 Require.js 來管理不同的模闆
- 使用 LocalStorage 來存儲使用者的資料
通過結合這一系列的工具,我們最終能夠實作一個複雜的單頁面應用。而這些。也就是今天我們看到的單頁面應用的基本元素。我們能夠在 Angular 應用、React 應用、Vue.js 應用 看到這些基本要素的影子。如:Vue Router、React Router、Angular 2 RouterModule 都是負責路由(頁面跳轉及子產品關系)的。在 Vue 和 React 裡。它們都是由輔助子產品來實作的。由于 React 僅僅是層 UI 層。而 Vue.js 也是用于建構使用者界面的架構。
路由:頁面跳轉與子產品關系
要說起路由。那但是有非常長的故事。
當我們在浏覽器上輸入網址的時候。我們就已經開始了各種路由的旅途了。
- 浏覽器會檢查有沒有對應的域名緩存,沒有的話就會一層層的去向 DNSserver 尋向,最後傳回對應的server的 IP 位址。
- 接着,我們請求的站點将會将由對應 IP 的 HTTP server處理。HTTP server會依據請求來交給對應的應用容器來處理。
- 随後。我們的應用将依據使用者請求的路徑,将請求交給對應的函數來處理。最後,傳回對應的 HTML 和資源文化
當我們做背景應用的時候。我們僅僅須要關心上述過程中的最後一步。即,将對應的路由交給對應的函數來處理。
這一點。在不同的背景架構的表現形式都是類似的。
如 Python 語言裡的 Web 開發架構 Django 的 URLConf,使用正規表達式來表正
url(r'^articles/2003/$', views.special_case_2003),
而在 Laravel 裡,則是通過參數的形式來呈現
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
//
});
雖然表現形式有一些差别,但是整體來說也是差點兒相同的。
而對于前端應用來說,也是如此,将對應的 URL 的邏輯交由對應的函數來處理。
React Router 使用了類似形式來處理路由。代碼例如以下所看到的:
<Route path="blog" component={BlogList} />
<Route path="blog/:id" component={BlogDetail} />
當頁面跳轉到 blog 的時候。會将控制權将給 BlogList 元件來處理。
當頁面跳轉到 blog/fasfasf-asdfsafd 的時候。将比對到這二個路由,并交給 BlogDetail 元件 來處理。而路由中的 id 值,也将作為參數 BlogDetail 元件來處理。
類似的,而 Angular 2 的形式則是:
{ path: 'blog', component: BlogListComponent },
{ path: 'blog/:id', component: BlogDetailComponent },
類似的,這裡的 BlogDetailComponent 是一個元件。path 中的 id 值将會傳遞給 BlogDetailComponent 元件。
從上面來看。雖然表現形式上有所差異,但是其行為是一緻的:使用規則引擎來處理路由與函數的關系。
稍有不同的是,背景的路由全然交由server端來控制,而前端的請求則都是在本地改變其狀态。
而且同一時候在不同的前端架構上,他們在行為上另一些差别。這取決于我們是否須要背景渲染,即重新整理目前頁面時的表現形式。
-
使用 Hash (#)或者 Hash Bang (#!) 的形式。
即 # 開頭的參數形式,諸如 ued.party/#/blog。當我們訪問 blog/12 時,URL 的就會變成 ued.party/#/blog/12
-
使用新的 HTML 5 的 history API。使用者看到的 URL 和正常的 URL 是一樣的。
當使用者點選某個連結進入到新的頁面時。會通過 history 的 pushState 來填入新的位址。當我們訪問 blog/12 時,URL 的就會變成 ued.party/blog/12。當使用者重新整理頁面的時候,請通過新的 URL 來向server請求内容。
幸運的是,大部分的最新 Router 元件都會推斷是否支援 history API,再來決定先用哪一個方案。
資料:擷取與鑒權
實作路由的時候,僅僅是将對應的控制權交給控制器(或稱元件)來處理。而作為一個單頁面應用的控制器。當運作到對應的控制器的時候,就能夠依據對應的 blog/12 來擷取到使用者想要的 ID 是 12。這個時候,控制器将須要在頁面上設定一個 loading 的狀态,然後發送一個請求到背景server。
對于資料擷取來說,我們能夠通過封裝過 XMLHttpRequest 的 Ajax 來擷取資料,也能夠通過新的、支援 Promise 的 Fetch API 來擷取資料。等等。Fetch API 與經過 Promise 封裝的 Ajax 并沒有太大的差别。我們仍然是寫類似于的形式:
fetch(url).then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))
對于複雜一點的資料互動來說,我們能夠通過 RxJS 來解決類似的問題。整個過程中,比較複雜的地方是對資料的鑒權與模型(Model)的處理。
模型麻煩的地方在于:轉變成想要的形式。
背景傳回的值是可變的,它有可能不傳回。有可能是 null,又或者是與我們要顯示的值不一樣——想要展示的是 54%,而背景傳回的是 0.54。與此同一時候。我們可能還須要對數值進行簡單的計算。顯示一個範圍、區間,又或者是不同的兩種展示。
同一時候在必要的時候。我們還須要将這些值存儲在本地,或者記憶體裡。當我們又一次進入這個頁面的時候,我們再去讀取這些值。
一旦談論到資料的時候,不可避免的我們就須要關心安全因素。
對于普通的 Web 應用來說,我們能夠做兩件事來保證資料的安全:
- 採用 HTTPS:在傳輸的過程中保證資料是加密的。
- 鑒權:確定指定的使用者僅僅能能夠訪問指定的資料。
眼下。流行的前端鑒權方式是 Token 的形式。能夠是普通的定制 Token,也能夠是 JSON Web Token。
擷取 Token 的形式。則是通過 Basic 認證——将使用者輸入的username和password,經過 BASE64 加密發送給server。server解密後驗證是否是正常的username和password,再傳回一個帶有時期期限的 Token 給前端。
随後,當使用者去擷取須要權限的資料時,須要在 Header 裡鑒定這個 Token 是否有限。再傳回對應的資料。假設 Token 已經過期了,則傳回 401 或者類似的标志。client就在這個時候清除 Token。并讓使用者又一次登入。
資料展示:模闆引擎
如今,我們已經擷取到這些資料了,下一步所須要做的就是顯示這些資料。
與其它内容相比。顯示資料就是一件簡單的事,無非就是:
- 依據條件來顯示、隐藏某些資料
- 在模闆中對資料進行周遊顯示
- 在模闆中運作方法來擷取對應的值,能夠是函數,也能夠是過濾器。
- 依據不同的數值來動态擷取樣式
- 等等
不同的架構會存在一些差異。而且現代的前端架構都能夠支援單向或者雙向的資料綁定。當對應的資料發生變化時,它就能夠自己主動地顯示在 UI 上。
最後,在對應須要處理的 UI 上,綁上對應的事件來處理。
僅僅是在資料顯示的時候,又會涉及到另外一個問題,即元件化。對于一些須要重用的元素。我們會将其抽取為一個通用的元件,以便于我們能夠複用它們。
而且在這些元件裡,也會涉及到對應的參數變化即狀态改變。
互動:事件與狀态管理
完畢一步步的渲染之後,我們還須要做的事情是:互動。互動分為兩部分:使用者互動、元件間的互動——共享狀态。
元件互動:狀态管理
使用者從 A 頁面跳轉到 B 頁面的時候。為了解耦元件間的關系,我們不會使用元件的參數來傳入值。
而是将這些值存儲在記憶體裡,在适當的時候調出這些值。當我們處理使用者是否登入的時候。我們須要一個 isLogined 的方法來擷取使用者的狀态。在使用者登入的時候。我們還須要一個 setLogin 的方法;使用者登出的時候,我們還須要更新一下使用者的登入狀态。
在沒有 Redux 之前。我都會寫一個 service 來管理應用的狀态。在這個子產品裡寫上些 setter、getter 方法來存儲狀态的值,并依據業務功能寫上一些來操作這個值。
然而,使用 service 時。我們非常難跟蹤到狀态的變化情況。還須要做一些額外的代碼來特别處理。
有時候也會犯懶一下,直接寫一個全局變量。
這個時候維護起代碼來就是一場噩夢,須要全局搜尋對應的變量。
假設是調用某個特定的 Service 就比較easy找到調用的地方。
使用者互動:事件
其實,對于使用者互動來說也僅僅是改變狀态的值。即對狀态進行操作。
舉一個樣例。當使用者點選登入的時候,發送資料到背景,由背景傳回這個值。由控制器一一的去改動這些狀态,最後确認這個使用者登入,并發一個使用者已經登入的廣播。又或者改動全局的使用者值。
節選自:我的職業是前端project師
轉載于:https://www.cnblogs.com/brucemengbm/p/7308029.html