前端程序猿必知:單頁面應用的核心

這幾年裏。單頁面應用的框架使人目不暇接,各類新的概念也層出不窮。從過去的 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 來講明代表當前所在的頁面。並擁有從一個頁面跳轉到另一個頁面的入口。

當移動設備的性能愈來愈好時,開發人員們開始在瀏覽器裏渲染頁面:

  • 使用 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 也是用於構建用戶界面的框架。

路由:頁面跳轉與模塊關係

要提及路由。那可是有很是長的故事。

當咱們在瀏覽器上輸入網址的時候。咱們就已經開始了各類路由的旅途了。

  1. 瀏覽器會檢查有沒有對應的域名緩存,沒有的話就會一層層的去向 DNSserver 尋向,最後返回對應的server的 IP 地址。

  2. 接着,咱們請求的站點將會將由對應 IP 的 HTTP server處理。HTTP server會依據請求來交給對應的應用容器來處理。

  3. 隨後。咱們的應用將依據用戶請求的路徑,將請求交給對應的函數來處理。最後,返回對應的 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 應用來講,咱們可以作兩件事來保證數據的安全:

  1. 採用 HTTPS:在傳輸的過程當中保證數據是加密的。

  2. 鑑權:確保指定的用戶僅僅能可以訪問指定的數據。

眼下。流行的前端鑑權方式是 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師

相關文章
相關標籤/搜索