我的認爲單頁面應用的優點至關明顯:javascript
固然,SPA也有它自身的缺點,例如不利於搜索引擎優化等等,這些問題也有其相應的解決方案。php
下面要介紹的這種方式能夠說是一種模式或者工做流,和前端使用什麼框架無關,也和後端使用什麼語言、數據庫無關。不能說是The Best Practice,我相信通過更多人的討論和思考會有A Better Practice。:)css
下圖展現了這種模式的整個先後端及各自的主要組成:html
看起來有點複雜,接下來會仔細地對上面每個部分進行解釋。看完本文,就應該能理解上圖中的各部件之間的交互流程。前端
把上圖的前端部分單獨抽出來進行研究:java
前端中大體分爲四種類型的模塊:git
component指的是頁面上的一個可複用UI交互單元,例如一個博客的評論功能:github
咱們能夠把博客評論作爲一個組件,這個組件有本身的結構(html),外觀(css),交互邏輯(js),因此咱們能夠單獨作一個叫comment的component,由如下文件組成:ajax
(每一個component能夠想象成一個工程,甚至能夠有本身的README、測試等)數據庫
一個component能夠依賴另一個component,這時候它們是父子關係;component之間也能夠互相組合,它們就是兄弟關係。最後的結果就相似DOM tree,component能夠組成components tree。
例如,如今要給這個博客添加兩個功能:
咱們構建兩個組件,reply和user-info-card。由於每一個comment都要有本身的回覆列表,因此comment組件是依賴於reply組件的,comment和reply組件是嵌套關係。
而user-info-card能夠出如今comment或者reply當中,而且爲了之後讓user-info-card複用性更強,它應該不屬於任何一個組件,它和其餘組件是組合關係。因此咱們就獲得一個簡單的componenets tree:
怎麼能夠作到鼠標放到評論和回覆的用戶頭像上顯示名片呢?這其實牽涉到組件之間是如何進行通訊的問題。
最佳的方式就是使用事件機制,全部組件之間能夠經過一個叫eventbus通用組件進行信息的交互。因此,要作到上述功能:
user-info-card:show
的事件。user-info-card:show
事件。user-info-card:
var eventbus = require("eventbus") eventbus.on("user-info-card:show", function(user) { // 顯示用戶名片 })
comment or reply:
var eventbus = require("eventbus") $avatar.on("mouseover", function(event) { eventbus.emit("user-info-card:show", userData) })
components之間用事件進行通訊的優點在於:
總結:component之間有嵌套和組合的關係,構成components tree;component之間經過事件進行信息、數據的交換。
component的渲染和顯示依賴於數據(model)。例如上面的評論,就會有一個評論列表的model。
comments: [ {user:.., content:.., createTime: ..}, {user:.., content:.., createTime: ..}, {user:.., content:.., createTime: ..} ]
每一個評論的component會對應一個comment(comments數組中的對象)進行渲染,渲染完之後就會正確地顯示在頁面上。
由於可能在其餘component中也會須要用到這些數據,因此comment component不會本身直接保存這些comment model。這些model都會保存在service當中,而component會從service拿取數據。components和services之間是多對多的關係:一個component可能會從不一樣的services中拿取數據,而一個service可能爲多個components提供數據。
services除了用於緩存數據之外,還提供一系列對數據的一些操做接口。能夠提供給components進行操做。這樣的好處在於保持了數據的一直性,假如你使用的是MVVM框架進行component的開發,對數據的操做還能夠直接對多個視圖產生數據綁定,當services中的數據變化了,多個components的視圖也會相應地獲得更新。
總結:services是對前端數據(也就是model)的緩存和操做。
而services中緩存的數據是從哪裏來的呢?固然也許想到的第一個方案是在services中直接發送Ajax請求去服務器中拉去數據。而這裏建議不直接這樣作,而是把各類和後端的API進行交互的接口封裝到一個叫databus的模塊當中,這裏的databus至關因而「對後端數據進行原子操做的集合」。
如上面的comment service須要從後端進行拉取數據,它會這樣作:
var databus = require("databus") var comments = null databus.getAllComments(function(cmts) { // 調用databus方法進行數據拉取 comments = cmts })
而databus中則封裝了一層Ajax
databus.getAllCommetns = function(callback) { utils.ajax({ url: "/comments", method: "GET", success: callback }) }
這樣作是由於,不一樣的services之間可能會用到一樣的接口對後端進行操做,把操做封裝起來能夠提升接口的複用性。注意,若是databus中的某些操做不涉及到servcies的數據,這操做也能夠被components所調用(例如退出、登陸等)。
總結:databus封裝了提供給services和component和後端API進行交互的接口。
這兩個模塊均可以被其餘組件所依賴。
common,故名思議,組件之間的共用數據和一些程序參數能夠緩存在這裏。
utils,封裝了一些可複用的函數,例如ajax等。
全部組件(特別是components之間)的經過事件機制進行數據、消息通訊的接口。能夠簡單地使用EventEmitter這個庫來實現。
傳統的網頁頁面通常都是由後端進行頁面的渲染,而在咱們的架構當中,後端只渲染一個頁面,其後,後端只是至關於一個Web Service,前端使用Ajax調用其接口進行數據的調取和操做,使用數據進行頁面的渲染。
這樣的好處就是,後端不只僅能處理Web端的頁面的請求,並且處理提供移動端、桌面端的請求或者做爲第三方開放接口來使用。大大提升後端處理請求的靈活性。
後端對比起前端的架構來講會簡單不少,可是這只是其中一種模式,對於不一樣複雜程度的應用可能會作相應的調整。後端大概分爲三層:
例如上面的comments的例子,CGI能夠接收到前端發送的請求:
var commentsBusiness = require("./businesses/comments") app.get("/comments", function(req, res) { // 此處調用comments的business數據庫操做 commentsBusiness.getAllComments(function(comments) { // 返回數據結果 res.json(comments) }) })
後端的API能夠採用更規範的RESTful API的方式,而RESTful不在本文的討論範圍內。有興趣的能夠參考Best Practices for Designing a Pragmatic RESTful API。
先後端的架構都基本清晰了,咱們來看看文章開頭的圖:
看着圖來,咱們總結一下整個先後端的交互流程:
一個好的工做流可讓開發事半功倍。上面的這種單頁面應用也有其相應的一種開發工做流,固然這種工做流也適合非單頁面應用:
先後端分離開發。建議均可以採用TDD(測試驅動開發)的方式來單獨測試、單獨開發(關於Web APP測試這一塊能夠單獨進行討論研究),提升產品的可靠性、穩定性。