模板,從服務端到客戶端

作者: Lars Kappert  來源: 伯樂在線  發布時間: 2014-06-24 09:45  閱讀: 5380 次  推薦: 15   原文鏈接   [收藏]  

  英文原文 Client-Side Templating

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

  為什么要使用模板?

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

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

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

  第一印象

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

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

<h1>{{title}}</h1>

 <ul>

     {{#names}}

         <li>{{name}}</li>

     {{/names}}

 </ul>

  如果你寫過HTML,那么你一定很熟悉上面的代碼。上文的HTML中有一些占位符。這些占位符將會被真實的數據取代。例如這個對象:

 var data = {

     "title": "Story",

     "names": [

         {"name": "Tarzan"},

         {"name": "Jane"}

     ]

 }

  把數據和模板結合起來,就會得到下面的HTML代碼:

 <h1>Story</h1>

 <ul>

     <li>Tarzan</li>

     <li>Jane</ul>

 </ul>

  將模板和數據分離開來對于維護HTML來說是一件好事。比如說,如果想要更改標簽或者添加類(class)就只需要更改模板就可以了。另外,對于需要迭代出現的元素(比如<li>),程序員只需要寫一次就好了。

  模板引擎

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

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

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

  Mustache實例

  上文中的例子可以借助模板引擎實現,例如使用了Mustache模板語法的mustache.js。關于這種語法更多信息,我會在后面告訴你的。現在先來看看下面的JS代碼能得到什么效果:

var template = '<h1>{{title}}</h1><ul>{{#names}}<li>{{name}}</li>{{/names}}</ul>';

var data = {"title": "Story", "names": [{"name": "Tarzan"}, {"name": "Jane"}]};

var result = Mustache.render(template, data);

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

document.body.innerHTML = result;

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

<script src="https://raw.github.com/janl/mustache.js/master/mustache.js"></script>

  組織模板

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

  但事情總不會十全十美的。如果一個項目中要使用非常多的模板,出于避免過多Ajax請求而影響性能的原因,我們不希望這么多文件被分開加載下來。

  場景1:腳本標簽

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

<script id="myTemplate" type="text/x-handlebars-template">

     <h1>{{title}}</h1>

     <ul>

         {{#names}}

             <li>{{name}}</li>

         {{/names}}

     </ul>

 </script>

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

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

var template = $('#myTemplate').html();

var compiledTemplate = Handlebars.compile(template);

var result = compiledTemplate(data);

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

  場景2:預編譯模板

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

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

 var myTemplates = {

     templateA: function() { ….},

     templateB: function() { ….};

     templateC: function() { ….};

 };

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

var result = myTemplates.templateB(data);

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

  Node.js示例

  任何模板預編譯腳本至少要滿足下面的要求:

  1. 讀取模板文件,
  2. 編譯模板,
  3. 最后的結果可以被合并入一個或多個文件、

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

 var fs = require('fs'),

     hogan = require('hogan.js');

 var templateDir = './templates/',

     template,

     templateKey,

     result = 'var myTemplates = {};';

 fs.readdirSync(templateDir).forEach(function(templateFile) {

     template = fs.readFileSync(templateDir + templateFile, 'utf8');

     templateKey = templateFile.substr(0, templateFile.lastIndexOf('.'));

     result += 'myTemplates["'+templateKey+'"] = ';

     result += 'new Hogan.Template(' + hogan.compile(template, {asString: true}) + ');'

 });

 fs.writeFile('compiled.js', result, 'utf8');

  這段代碼先是讀取了在templates目錄下所有的文件,再編譯了這些模板,最后將它們寫入compiled.js。

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

  場景3:AMD和RequireJS

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

  在使用模塊時,RequireJS有一個text插件用于規定基于文本的依賴。默認是將AMD的依賴當做JavaScript來處理,不過模板并不是JS而是文本(比如HTML格式的模板),所以我們需要用上這個插件:

 define(['handlebars', 'text!templates/myTemplate.html'], function(Handlebars, template) {

     var myModule = {

         render: function() {

             var data = {"title": "Story", "names": [{"name": "Tarzan"}, {"name": "Jane"}]};

             var compiledTemplate = Handlebars.compile(template);

             return compiledTemplate(data);

         }

     };

     return myModule;

 });

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

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

  監聽模板

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

define(['templates/myTemplate.js'], function(compiledTemplate) {

     var myModule = {

         render: function() {

             var data = {"title": "Story", "names": [{"name": "Tarzan"}, {"name": "Jane"}]};

             return compiledTemplate(data);

         };

     };

     return myModule;

 }

  性能問題

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

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

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

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

  結論

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

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

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

15
1
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()