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

LamBoisvert 7年前發布 | 159K 次閱讀 程序員 jQuery jQuery插件

這幾年里,單頁面應用的框架令人應接不暇,各種新的概念也層出不窮。從過去的 jQuery Mobie、Backbone 到今天的 Angular 2、React、Vue 2,除了版本號不同,他們還有很多的相同之處。

剛開始寫商業代碼的時候,我使用的是 jQuery。使用 jQuery 來實現功能很容易,找到一個相應的 jQuery 插件,再編寫相應的功能即可。對于單頁面應用亦是如此,尋找一個相輔助的插件就可以了,如 jQuery Mobile。

盡管在今天看來,jQuery Mobile 已經不適合于今天的多數場景了。這個主要原因是,當時的用戶對于移動 Web 應用的理解和今天是不同的。他們覺得移動 Web 應用就是針對移動設備而訂制的,移動設備的 UI、更快的加載速度等等。而在今天,多數的移動 Web 應用,幾乎都是單頁面應用了。

過去,即使我們想創建一個單頁面應用,可能也沒有一個合適的方案。而在今天,可選擇的方案就多了。每個人在不同類型的項目上,也會有不同的方案,沒有一個框架能解決所有的問題

  • 對于工作來說,我更希望的是一個完整的解決方案。

  • 對于編程體驗來說,我喜歡一點點的去創造一些輪子。

當我們 會用的框架越多的時候, 所花費的時間抉擇也就越多 。而單頁面應用的都有一些相同的元素,對于這些基本元素的理解,可以讓我們更快的適合其他框架。

單頁面應用的演進

我接觸到單頁面應用的時候,它看起來就像是 將所有的內容放在一個頁面上么

只需要在一個 HTML 寫好所需要的各個模板,并在不同的頁面上 data-role 表明這是個頁面(基于 jQuery Mobile)——每個定義的頁面都和今天的移動應用的模式相似,有 header、content、footer 三件套。再用 id 來定義好相應的路由。

<div data-role="page" id="foo">
...
</div>

這樣我們就在一個 HTML 里返回了所有的頁面了。隨后,只需要在在入口處的 href 里,寫好相應的 ID 即可。

<a href="#foo">跳轉到foo</a>

當我們點擊相應的鏈接時,就會切換到 HTML 中相應的 ID。這種簡單的單頁面應用基本上就是一個離線應用了,只適合于簡單的場景,可是它帶有單頁面應用的基本特性。而復雜的應用,則需要從服務器獲取數據。然而早期受限于移動瀏覽器性能的影響,只能從服務器獲取相應的 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. 瀏覽器會檢查有沒有相應的域名緩存,沒有的話就會一層層的去向 DNS服務器 尋向,最后返回對應的服務器的 IP 地址。

  2. 接著,我們請求的網站將會將由對應 IP 的 HTTP 服務器處理,HTTP 服務器會根據請求來交給對應的應用容器來處理。

  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 組件。

從上面來看,盡管表現形式上有所差異,但是其行為是一致的:使用規則引擎來處理路由與函數的關系。稍有不同的是,后臺的路由完全交由服務器端來控制,而前端的請求則都是在本地改變其狀態。

并且同時在不同的前端框架上,他們在行為上還有一些區別。這取決于我們是否需要后臺渲染,即刷新當前頁面時的表現形式。

  • 使用 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 來向服務器請求內容。

幸運的是,大部分的最新 Router 組件都會判斷是否支持 history API,再來決定先用哪一個方案。

數據:獲取與鑒權

實現路由的時候,只是將對應的控制權交給控制器(或稱組件)來處理。而作為一個單頁面應用的控制器,當執行到相應的控制器的時候,就可以根據對應的 blog/12 來獲取到用戶想要的 ID 是 12。這個時候,控制器將需要在頁面上設置一個 loading 的狀態,然后發送一個請求到后臺服務器。

對于數據獲取來說,我們可以通過封裝過 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 認證——將用戶輸入的用戶名和密碼,經過 BASE64 加密發送給服務器。服務器解密后驗證是否是正常的用戶名和密碼,再返回一個帶有時期期限的 Token 給前端。

隨后,當用戶去獲取需要權限的數據時,需要在 Header 里鑒定這個 Token 是否有限,再返回相應的數據。如果 Token 已經過期了,則返回 401 或者類似的標志,客戶端就在這個時候清除 Token,并讓用戶重新登錄。

數據展示:模板引擎

現在,我們已經獲取到這些數據了,下一步所需要做的就是顯示這些數據。

與其他內容相比,顯示數據就是一件簡單的事,無非就是:

  • 依據條件來顯示、隱藏某些數據

  • 在模板中對數據進行遍歷顯示

  • 在模板中執行方法來獲取相應的值,可以是函數,也可以是過濾器。

  • 依據不同的數值來動態獲取樣式

  • 等等

不同的框架會存在一些差異。并且現代的前端框架都可以支持單向或者雙向的數據綁定。當相應的數據發生變化時,它就可以自動地顯示在 UI 上。

最后,在相應需要處理的 UI 上,綁上相應的事件來處理。

只是在數據顯示的時候,又會涉及到另外一個問題,即組件化。對于一些需要重用的元素,我們會將其抽取為一個通用的組件,以便于我們可以復用它們。

<my-sizer [(size)]="fontSizePx"></my-sizer>

并且在這些組件里,也會涉及到相應的參數變化即狀態改變。

交互:事件與狀態管理

完成一步步的渲染之后,我們還需要做的事情是:交互。交互分為兩部分:用戶交互、組件間的交互——共享狀態。

組件交互:狀態管理

用戶從 A 頁面跳轉到 B 頁面的時候,為了解耦組件間的關系,我們不會使用組件的參數來傳入值。而是將這些值存儲在內存里,在適當的時候調出這些值。

當我們處理用戶是否登錄的時候,我們需要一個 isLogined 的方法來獲取用戶的狀態;在用戶登錄的時候,我們還需要一個 setLogin 的方法;用戶登出的時候,我們還需要更新一下用戶的登錄狀態。

在沒有 Redux 之前,我都會寫一個 service 來管理應用的狀態。在這個模塊里寫上些 setter、getter 方法來存儲狀態的值,并根據業務功能寫上一些來操作這個值。然而,使用 service 時,我們很難跟蹤到狀態的變化情況,還需要做一些額外的代碼來特別處理。

有時候也會犯懶一下,直接寫一個全局變量。這個時候維護起代碼來就是一場噩夢,需要全局搜索相應的變量。如果是調用某個特定的 Service 就比較容易找到調用的地方。

用戶交互:事件

事實上,對于用戶交互來說也只是改變狀態的值,即對狀態進行操作。

舉一個例子,當用戶點擊登錄的時候,發送數據到后臺,由后臺返回這個值。由控制器一一的去修改這些狀態,最后確認這個用戶登錄,并發一個用戶已經登錄的廣播,又或者修改全局的用戶值。

 

 

 

來自:https://mp.weixin.qq.com/s?__biz=MjM5Mjg4NDMwMA==&mid=2652974800&idx=1&sn=afc003d743f9e32224141a113c400742&chksm=bd4afdf38a3d74e5bdacb0ab97eb0de50dd71a378ac642fb38632d9590185e53ede6d2781198#rd

 

 本文由用戶 LamBoisvert 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!