這幾年裏。單頁面應用的框架使人目不暇接,各類新的概念也層出不窮。從過去的 jQuery Mobie、Backbone 到今天的 Angular 二、React、Vue 2,除了版本不一樣,他們還有很是多的一樣之處。php
剛開始寫商業代碼的時候,我使用的是 jQuery。使用 jQuery 來實現功能很是easy,找到一個對應的 jQuery 插件,再編寫對應的功能就能夠。css
對於單頁面應用亦是如此,尋找一個相輔助的插件就可以了,如 jQuery Mobile。前端
雖然在今天看來。jQuery Mobile 已經不適合於今天的多數場景了。這個主要緣由是,當時的用戶對於移動 Web 應用的理解和今天是不一樣的。他們認爲移動 Web 應用就是針對移動設備而訂製的。移動設備的 UI、更快的載入速度等等。python
而在今天,多數的移動 Web 應用,差點兒都是單頁面應用了。git
過去。即便咱們想建立一個單頁面應用,可能也沒有一個合適的方案。而在今天,可選擇的方案就多了(PS:參見《第四章:學習前端僅僅需要三個月【框架篇】》)。每個人在不一樣類型的項目上,也會有不一樣的方案,沒有一個框架能解決所有的問題github
當咱們會用的框架越多的時候, 所花費的時間抉擇也就越多。而單頁面應用的都有一些一樣的元素。對於這些基本元素的理解,可以讓咱們更快的適合其它框架。編程
我接觸到單頁面應用的時候,它看起來就像是將所有的內容放在一個頁面上麼。僅僅需要在一個 HTML 寫好所需要的各個模板,並在不一樣的頁面上 data-role 代表這是個頁面(基於 jQuery Mobile)——每個定義的頁面都和今天的移動應用的模式相似,有 header、content、footer 三件套。json
再用 id 來定義好對應的路由。瀏覽器
<div data-role="page" id="foo">
...
</div>
這樣咱們就在一個 HTML 裏返回了所有的頁面了。緩存
隨後,僅僅需要在在入口處的 href 裏,寫好對應的 ID 就能夠。
<a href="#foo">跳轉到foo</a>
當咱們點擊對應的連接時,就會切換到 HTML 中對應的 ID。這種簡單的單頁面應用基本上就是一個離線應用了。僅僅適合於簡單的場景,可是它帶有單頁面應用的基本特性。而複雜的應用。則需要從server獲取數據。然而早期受限於移動瀏覽器性能的影響,僅僅能從server獲取對應的 HTML。並替換當前的頁面。
在這種應用中。咱們可以看到單頁面應用的基本元素: 頁面路由,經過某種方式。如 URL hash 來講明代表當前所在的頁面。並擁有從一個頁面跳轉到另一個頁面的入口。
當移動設備的性能愈來愈好時,開發人員們開始在瀏覽器裏渲染頁面:
經過結合這一系列的工具,咱們最終可以實現一個複雜的單頁面應用。而這些。也就是今天咱們看到的單頁面應用的基本元素。咱們可以在 Angular 應用、React 應用、Vue.js 應用 看到這些基本要素的影子。如:Vue Router、React Router、Angular 2 RouterModule 都是負責路由(頁面跳轉及模塊關係)的。在 Vue 和 React 裏。它們都是由輔助模塊來實現的。因爲 React 僅僅是層 UI 層。而 Vue.js 也是用於構建用戶界面的框架。
要提及路由。那可是有很是長的故事。
當咱們在瀏覽器上輸入網址的時候。咱們就已經開始了各類路由的旅途了。
當咱們作後臺應用的時候。咱們僅僅需要關心上述過程當中的最後一步。即,將對應的路由交給對應的函數來處理。
這一點。在不一樣的後臺框架的表現形式都是相似的。
如 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端來控制,而前端的請求則都是在本地改變其狀態。
並且同一時候在不一樣的前端框架上,他們在行爲上另外一些差異。這取決於咱們是否需要後臺渲染,即刷新當前頁面時的表現形式。
即 # 開頭的參數形式,諸如 ued.party/#/blog。當咱們訪問 blog/12 時,URL 的就會變成 ued.party/#/blog/12
當用戶點擊某個連接進入到新的頁面時。會經過 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 應用來講,咱們可以作兩件事來保證數據的安全:
眼下。流行的前端鑑權方式是 Token 的形式。可以是普通的定製 Token,也可以是 JSON Web Token。
獲取 Token 的形式。則是經過 Basic 認證——將用戶輸入的username和password,通過 BASE64 加密發送給server。server解密後驗證是不是正常的username和password,再返回一個帶有時期期限的 Token 給前端。
隨後,當用戶去獲取需要權限的數據時,需要在 Header 裏鑑定這個 Token 是否有限。再返回對應的數據。假設 Token 已通過期了,則返回 401 或者相似的標誌。client就在這個時候清除 Token。並讓用戶又一次登陸。
現在,咱們已經獲取到這些數據了,下一步所需要作的就是顯示這些數據。
與其它內容相比。顯示數據就是一件簡單的事,無非就是:
不一樣的框架會存在一些差別。並且現代的前端框架都可以支持單向或者雙向的數據綁定。當對應的數據發生變化時,它就可以本身主動地顯示在 UI 上。
最後,在對應需要處理的 UI 上,綁上對應的事件來處理。
僅僅是在數據顯示的時候,又會涉及到另一個問題,即組件化。對於一些需要重用的元素。咱們會將其抽取爲一個通用的組件,以便於咱們可以複用它們。
<my-sizer [(size)]="fontSizePx"></my-sizer>
並且在這些組件裏,也會涉及到對應的參數變化即狀態改變。
完畢一步步的渲染以後,咱們還需要作的事情是:交互。交互分爲兩部分:用戶交互、組件間的交互——共享狀態。
用戶從 A 頁面跳轉到 B 頁面的時候。爲了解耦組件間的關係,咱們不會使用組件的參數來傳入值。
而是將這些值存儲在內存裏,在適當的時候調出這些值。當咱們處理用戶是否登陸的時候。咱們需要一個 isLogined 的方法來獲取用戶的狀態。在用戶登陸的時候。咱們還需要一個 setLogin 的方法;用戶登出的時候,咱們還需要更新一下用戶的登陸狀態。
在沒有 Redux 以前。我都會寫一個 service 來管理應用的狀態。在這個模塊裏寫上些 setter、getter 方法來存儲狀態的值,並依據業務功能寫上一些來操做這個值。
然而,使用 service 時。咱們很是難跟蹤到狀態的變化狀況。還需要作一些額外的代碼來特別處理。
有時候也會犯懶一下,直接寫一個全局變量。
這個時候維護起代碼來就是一場噩夢,需要全局搜索對應的變量。
假設是調用某個特定的 Service 就比較easy找到調用的地方。
其實,對於用戶交互來講也僅僅是改變狀態的值。即對狀態進行操做。
舉一個樣例。當用戶點擊登陸的時候,發送數據到後臺,由後臺返回這個值。由控制器一一的去改動這些狀態,最後確認這個用戶登陸,併發一個用戶已經登陸的廣播。又或者改動全局的用戶值。
節選自:個人職業是前端project師