天天看點

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

這幾年裡。單頁面應用的架構令人應接不暇,各種新的概念也層出不窮。從過去的 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 也是用于建構使用者界面的架構。

路由:頁面跳轉與子產品關系

要說起路由。那但是有非常長的故事。

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

  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 上,綁上對應的事件來處理。

僅僅是在資料顯示的時候,又會涉及到另外一個問題,即元件化。對于一些須要重用的元素。我們會将其抽取為一個通用的元件,以便于我們能夠複用它們。

而且在這些元件裡,也會涉及到對應的參數變化即狀态改變。

互動:事件與狀态管理

完畢一步步的渲染之後,我們還須要做的事情是:互動。互動分為兩部分:使用者互動、元件間的互動——共享狀态。

元件互動:狀态管理

使用者從 A 頁面跳轉到 B 頁面的時候。為了解耦元件間的關系,我們不會使用元件的參數來傳入值。

而是将這些值存儲在記憶體裡,在适當的時候調出這些值。當我們處理使用者是否登入的時候。我們須要一個 isLogined 的方法來擷取使用者的狀态。在使用者登入的時候。我們還須要一個 setLogin 的方法;使用者登出的時候,我們還須要更新一下使用者的登入狀态。

在沒有 Redux 之前。我都會寫一個 service 來管理應用的狀态。在這個子產品裡寫上些 setter、getter 方法來存儲狀态的值,并依據業務功能寫上一些來操作這個值。

然而,使用 service 時。我們非常難跟蹤到狀态的變化情況。還須要做一些額外的代碼來特别處理。

有時候也會犯懶一下,直接寫一個全局變量。

這個時候維護起代碼來就是一場噩夢,須要全局搜尋對應的變量。

假設是調用某個特定的 Service 就比較easy找到調用的地方。

使用者互動:事件

其實,對于使用者互動來說也僅僅是改變狀态的值。即對狀态進行操作。

舉一個樣例。當使用者點選登入的時候,發送資料到背景,由背景傳回這個值。由控制器一一的去改動這些狀态,最後确認這個使用者登入,并發一個使用者已經登入的廣播。又或者改動全局的使用者值。

節選自:我的職業是前端project師

轉載于:https://www.cnblogs.com/brucemengbm/p/7308029.html

繼續閱讀