模板,從服務端到客戶端

kmokidd發表於2014-06-12

編注:這是 2012 年的一篇舊文。

在瀏覽器中使用模板是一個日漸熱門的趨勢。將服務端的邏輯應用到客戶端上,還有越來越多的類MVC模式(模型-檢視-控制器:model-view-controller)的使用都使得在瀏覽器中“模板”的角色越來越重要。在過去,“模板”從來都是服務端的事情,但事實上在客戶端開發中,模板的作用是非常強大又具有表現力的。

為什麼要使用模板?

大體上來說,藉助模板是一種能很好地將檢視(views)中標記和邏輯分開的方法,還能將程式碼的重用性和可維護性最大化。如果使用的是語法與最終所得結果很相近的語言(比如HTML),你就能又快又好地把任務完成了。雖然模板可以用來輸出任何形式的文字,但由於我們想要討論的客戶端開發是有關於HTML的,所以在這篇文章裡,我們還是以HTML作為例子。

現在的動態應用中,客戶端常常需要頻繁地重新整理介面。這個效果可以通過服務端將HTML片段插入到客戶端的文件中。這樣做的話,伺服器要能支援傳送HTML的片段(與之相對:傳送完整的頁面)。還有就是,作為一個要處理這些標記片段的客戶端的開發者,你應該會想能完全控制你的模板。而模板引擎(Smarty)、流量(Velocity)還有ASP這些伺服器端的內容你都不用瞭解,也不用管那些“麵條式程式碼”(spaghetti code):例如在HTML文件裡是不是出現的臭名昭著的<?或者<%。

那麼現在來看看客戶端模板吧。

第一印象

對初學者而言,理解“模板”的含義很重要,foldoc(免費線上計算機詞典)中的解釋是:模板是一種文件,不過文件中有形參,再通過模板處理系統的特定語法用實參代替形參。

讓我們來看看最基本的模板長什麼樣子:

如果你寫過HTML,那麼你一定很熟悉上面的程式碼。上文的HTML中有一些佔位符。這些佔位符將會被真實的資料取代。例如這個物件:

把資料和模板結合起來,就會得到下面的HTML程式碼:

將模板和資料分離開來對於維護HTML來說是一件好事。比如說,如果想要更改標籤或者新增類(class)就只需要更改模板就可以了。另外,對於需要迭代出現的元素(比如<li>),程式設計師只需要寫一次就好了。

模板引擎

模板的語法是根據你需要的模板引擎來決定的(例如:佔位符{{title}})。引擎是負責分析模板,用提供的資料替換佔位符(變數、函式、迴圈等等)。

有些模板引擎看起來沒有什麼邏輯性。這指的不是在模板中只能插入簡單的佔位符,而是說智慧標籤(intelligent tags)方面的特性很少(比如陣列迭代器,條件渲染等等)。有些引擎就有很多特性和很好的可擴充套件性。關於這一點就不在這展開講了,你需要問問自己,在模板中你是否需要、需要多少邏輯。

每個模板引擎都有自己的API,不過通常你都能找到像render()和compile()這樣的方法。渲染的過程就是將真正的資料放入模板然後呈現出來。也就是說,渲染就是用真正的資料替代了佔位符。如果在此期間木板上有什麼邏輯,就會被執行。編譯模板指的是解析模板,然後將它轉換成一個JavaScript函式。模板中的邏輯都會被解釋為純JS(plain JavaScript),給定的資料會被傳入這些JS函式中,這麼做可以最大程度地優化HTML。

Mustache例項

上文中的例子可以藉助模板引擎實現,例如使用了Mustache模板語法的mustache.js。關於這種語法更多資訊,我會在後面告訴你的。現在先來看看下面的JS程式碼能得到什麼效果:

現在我們需要在頁面上顯示模板,你需要寫這麼一行程式碼:

第一個客戶端模板就完成了!在程式碼檔案中加入下面這句,你就可以試一試上面的例子了,或者看下線上演示

組織模板

如果你和我一樣,不喜歡HTML文件裡出現很長的內容,既造成了閱讀的困難還增加了維護的負擔。理想情況下,我們可以把模板分開維護,既能享受模板的語法高亮的便利,又能保證HTML的可讀性。

但事情總不會十全十美的。如果一個專案中要使用非常多的模板,出於避免過多Ajax請求而影響效能的原因,我們不希望這麼多檔案被分開載入下來。

場景1:指令碼標籤

常見的解決方案就是把所有的模板直接放在<scrpit>標籤中,<script>標籤的可選型別要稍作更改,比如改成type=”type/template”(瀏覽器在渲染或解析時會將這個屬性忽略)。

這樣的做,你就可以把所有的模板都放在HTML文件中,避免了額外的Ajax請求。

script標籤中的內容會後面被JavaScript當做模板來使用。請看下面的程式碼,這次我們用的是Handlebars模板引擎再結合一些jQuery,模板就用剛剛的裡的。也可以直接看線上演示

最終效果和上文的Mustache例子是一樣的。Handlebars也可以使用Mustache格式的模板,所以在這裡我們就用一樣的模板了。不過要注意,它們之間還是有一個很重要的區別:Handlebars是先得到一箇中間結果,再通過這個中間值得到HTML的。它先是將模板編譯成一個JS函式(稱之為compiledTemplate),然後資料再被傳入這個函式中執行,再返回最終結果。

場景2:預編譯模板

雖然說將渲染模板包裝在一個方法裡看起來要方便多了,但是將編譯和渲染分開也有顯而易見的優點。最重要的是,分開以後,可以把編譯放在伺服器端完成。我們可以在伺服器上執行JS程式碼(比如使用Node),有些模板引擎支援這樣的預編譯。

我們可以用一個JS文件(叫它comiled.js吧)將多個預編譯好的檔案放在一起。這個檔案的內容看起來可能是這樣的:

然後在應用中,我們只需要將資料傳入這些預編譯好的模板中:

這個方法遠比上文中討論過的將所有的模板放在<script type=”text/javascript”>中要好,客戶端會忽略編譯過程。取決於你的應用套件(application stack),這個解決方式並不一定很難實現,我們會在下文看到它具體的實現。

Node.js示例

任何模板預編譯指令碼至少要滿足下面的要求:

  1. 讀取模板檔案,
  2. 編譯模板,
  3. 最後的結果可以被合併入一個或多個檔案、

下文中的Node.js指令碼就實現了上面說的那3點(使用Hogan.js模板引擎):

這段程式碼先是讀取了在templates目錄下所有的檔案,再編譯了這些模板,最後將它們寫入compiled.js。

注意!現在得到的結果是完全沒有優化過的程式碼,也沒有做任何錯誤處理。不過它還是完成我們想要它做的事,也不需要很長的程式碼來預編譯模板。

場景3:AMD和RequireJS

隨著非同步牽引模組(通常我們都稱之為AMD)越來越多地被使用,為了更好地組織你的APP,建議將模組解耦。RequireJS是現在主流的模組載入器之一,在模組定義中,你可以特定某些依賴,在實際的模組裡你就可以使用它們了(工廠模式)。

在使用模組時,RequireJS有一個text外掛用於規定基於文字的依賴。預設是將AMD的依賴當做JavaScript來處理,不過模板並不是JS而是文字(比如HTML格式的模板),所以我們需要用上這個外掛:

這樣,就能在單獨的檔案中管理各個模板了,雖然這麼做是挺好的,但無疑增加了很多額外的Ajax請求,而且仍然需要在客戶端編譯模板。但是,可以用RequireJS中的r.js來優化這些額外的請求。這個決定了依賴,將模板或者依賴植入模組定義中,大大減小了請求數。

你會發現我們還沒有說到預處理,事實上有兩個方法可以完成預處理。可以寫一個r.js的外掛或者別的程式來預處理模板。這麼做的話就會改動了模組定義:我們需要在優化之前先使用一個模板*字串*,然後再使用一個模板*方法*。不過這些問題也不是很難處理,你可以去檢測它的變數型別或者將邏輯抽象出來(寫在外掛中或者直接寫在應用中)。

監聽模板

在場景2和場景3中,如果將模板當做未編譯的資源我們還能將應用構建地更好。就像你在寫CoffeeScript、Less或者SCSS,在開發時,可以監聽模板檔案的變化,一旦發現檔案出現變化,就立刻自動重新編譯,就像從CoffeeScript編譯到JavaScript一樣。這樣我們在程式碼中處理的模板都是已經預編譯過了的,還方便了在開發過程匯中將預編譯模板做相關的內聯優化。

 

效能問題

用客戶端模板完成UI更新時的渲染是常見的方法。還是那句話,想要達到效能最優,那就要在第一次請求頁面時儘可能少的請求額外的資源。這樣瀏覽器在渲染HTML頁面時不會因為要去載入JS資源或者別的資料而中斷渲染。這聽起來挺難的,特別是在又要動態載入內容又要儘可能減少載入時間的頁面上。理想情況下,模板是既可以在客戶端也可以在服務端使用的,這樣可以提供最優的效能還能保持它的可維護性。

有兩個問題還需要考慮一下:

  1. 我的應用中哪裡是有最多動態載入的呢?又是哪部分需要最短的載入時間的呢?
  2. 處理種種問題的程式是要放在客戶端還是服務端呢?

實際問題實際分析。確實使用預處理過的模板,客戶端可以比較輕易地快速渲染出效果。但是如果你需要重用模板,你會偏愛邏輯較少的模板一些。

結論

我們已經看到了客戶端模板的種種好處,比如:

  • 伺服器和API最好只負責提供資料(比如JSON);客戶端模板就能直接把資料套上了。
  • 客戶端方向的開發者可以自如地使用HTML和JS。
  • 使用模板的話,你就必須把邏輯和表現分離開。
  • 模板可以預編譯好然後快取起來,這樣伺服器每次都只要傳送資料就可以了。/li>
  • 不在伺服器端渲染而在客戶端渲染,多少會影響效能。

上述的文字已經介紹了很多關於(客戶端)模板的知識,希望現在你對這些內容有了更深的認識。

相關文章