handlebars用法

爲何須要模板引擎

關於前端的模板引擎,我用一個公式來解釋html

 模板引擎 模板 + 數據 ========> html頁面

模板引擎就像是html的解析生成器,將對應的模板填充完數據以後生成靜態的html頁面。它能夠在瀏覽器端(好比angular中指令所用的模板)也能夠在服務器端執行,不過通常用於服務器端。由於它的一個做用是抽象公共頁面來重用,若是在服務端填充數據,能夠減小回填數據給頁面的ajax請求,從而提高瀏覽器端總體頁面渲染速度。前端

那些年我用過的模板引擎

接觸過的模板引擎不算多,最先應該是jsp,本質上也是一種模板引擎,再到功能稍微強大的freemarker,這兩種都是屬於java語系的。js語系的jade和ejs我都有所接觸,不過不經常使用,jade那種類python的語法規則以及較低的解析效率都讓我不敢興趣,Express框架也只是早起將其做爲模板引擎。後來換成了強大的ejs,不管是功能仍是寫法上都接近jsp了。直到最新的Express4發佈,默認改成了弱邏輯的比較簡潔的模板引擎handlebars。java

我使用handlebars有如下幾個緣由:node

  • 此次新項目前端框架搭建基於Express4,模板引擎只能在ejs/jade/hogan/hbs中選擇一個。
  • 默認是handlebars,雖不知道緣由,想必有其緣由。
  • 看過「去哪兒」的前端技術分享,他們就是在handlebars上進行封裝的,證實已經有人填過坑了,能夠一試。
  • 開始比較看好ejs,可是官網文檔被強了,相比之下handlebars的文檔比較清晰,還有實例,雖然邏輯結構比較混亂,可是基本無障礙。

碼解handlebars

運行環境:Express四、hbs4 未接觸Express或hbs的能夠先看這裏python

初級玩家:表達式

數據:git

{ title: 'Express', obj:{ version: 'v4.3', category: 'node', "date~": '2016' } }

模板:github

<p>{ {title} }</p> <p>{ {obj.version} }</p> <p>{ {obj/category} }</p> <p>{ {obj.date~} }</p>

html頁面:ajax

Express v4.3 node

handlebars中變量都添加雙花括號來表示(相似Angular),對比ejs的"<%%>「來講看起來沒什麼區別,其實這是很人性化的,想一下你鍵盤上的位置,再考慮按這幾個字符的難易程度你就懂了。 其中要訪問變量的屬性值時能夠用相似json格式的」.",也能夠用"/"。express

其中變量名不可包含如下字符。若是包含則不被解析,如上的"{{obj.date~}}"。編程

空格 ! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~

但能夠用 " , ’ , [] 來轉譯這些特殊字符。

這一條規則意味着 「&&」,"||","!"這類邏輯判斷是不能出如今表達式中的! (看着這一條是否是以爲弱爆了,要否則怎麼叫若邏輯模板引擎呢~哈哈,不過固然有另外的解決辦法)。

中級玩家:helper

英語水平有限,實在找不到一個恰當的詞來翻譯它了。能夠理解爲它是注入到模板中的一個函數,用來接收參數並進行邏輯處理。

默認helper

if else

 { {#if author} } <h1>{ {firstName} } { {lastName} }</h1> { {else} } <h1>Unknown Author</h1> { {/if} }
{ {#if isActive} } <img src="star.gif" alt="Active"> { {else if isInactive} } <img src="cry.gif" alt="Inactive"> { {/if} }

和通常的編程語言的 if-else 代碼塊是差很少的,不過再次重申因爲上面提到的特殊字符,因此if條件中是不能有邏輯表達式的,只能是變量或者值。

unless

仍是由於上面提到的那些字符,handlebars不支持邏輯非("!"),因此又有了一個與if相反的helper

 { {#unless license} } <h3 class="warning">WARNING: This entry does not have a license!</h3> { {/unless} }

上面這段代碼就等價於

{ {#if license} } { {else} } <h3 class="warning">WARNING: This entry does not have a license!</h3> { {/if} }

each

都知道each至關於for循環。不過有些地方須要注意:

  • 能夠用相對路徑的方式來獲取上一層的上下文。(上下文概念跟js中的上下文差很少,好比在each passage代碼塊內,每一次循環上下文一次是passage[0],passage[1]…)
  • 一些默認變量,@first/@last 當該對象爲數組中第一個/最後一個時返回真值。若是數組成員爲值而非對象,@index表示當前索引值,能夠用@key或者this獲取當前值
  • 能夠用 as |xxx|的形式給變量起別名,循環中經過別名能夠引用父級變量值。固然也能夠經過相對路徑的方式引用父級變量。
{ {#each passage} } { {#each paragraphs} } { {@../index} }:{ {@index} }:{ {this} }</p> { {else} } <p class="empty">No content</p> { {/each} } { {/each} }
{ {#each array as |value, key|} } { {#each child as |childValue, childKey|} } { {key} } - { {childKey} }. { {childValue} } { {/each} } { {/each} }

同時也能夠用來遍歷對象,這時@key表示屬性名,this表示對應的值

{ {#each object} } { {@key} }: { {this} } { {/each} }

with

相似js中的with,能夠配合分頁使用,限定做用域。

{ {#with author as |myAuthor|} } <h2>By { {myAuthor.firstName} } { {myAuthor.lastName} }</h2> { {else} } <p class="empty">No content</p> { {/with} }

lookup

這個用於如下這種並列數組的狀況,能夠按照索引來找兄弟變量對應的值。理解起來有些困難,直接看代碼

{ groups: [ {id: 1, title: "group1"}, {id: 2, title: "group2"}, ], users: [ {id:1, login: "user1", groupId: 1}, {id:2, login: "user2", groupId: 2}, {id:3, login: "user3", groupId: 1} ], infos: [ 'a','b','c' ] }
<table> { {#each users} } <tr data-id="{ {id} }"> <td>{ {login} }</td> <td data-id="{ {groupId} }">{ {lookup ../infos @index} }</td> </tr> { {/each} } </table>
user1 a user2 b user3 c

這裏在users數組中按照索引值引用infos數組中對應的值,若是想引用groups中的groupId呢?很簡單,用with。

<table> { {#each users} } <tr data-id="{ {id} }"> <td>{ {login} }</td> <td data-id="{ {groupId} }">{ {#with (lookup ../groups @index)} }{ {title} }{ {/with} }</td> </tr> { {/each} } </table>

自定義helper

內置的helper不夠強大,因此一般須要寫js代碼自定義helper,先看一個簡單的單行helper。

行級helper

傳值

數值、字符串、布爾值這種常規數據能夠直接傳入,同時也能夠傳遞JSON對象(但只能傳一個),以key=value這種形式寫在後面,最後就能夠經過參數的hash屬性來訪問了。

模板

{ {agree_button "My Text" class="my-class" visible=true counter=4} }

代碼

hbs.registerHelper('agree_button', function() { console.log(arguments[0]);//==>"My Text" console.log(arguments[1].hash);//==>{class:"my-class",visible:true,conter:4} }
傳變量

傳變量時能夠用this指針來指代它訪問屬性,經過邏輯判斷後能夠返回一段html代碼,不過太建議這樣作。考慮之後的維護性,這種html代碼和js代碼混合起來的維護性是比較差的,若是要抽象層組件仍是使用分頁比較好。

模板:

{ {agree_button person} }

註冊helper:

hbs.registerHelper('agree_button', function(p) { console.log(p===this);//==> true var blog = hbs.handlebars.escapeExpression(this.person.blog), name = hbs.handlebars.escapeExpression(this.person.name); return new hbs.handlebars.SafeString( "<a href='"+blog+"'>"+ name + "</button>" ); });

數據:

var context = { person:{name: "哈哈哈", blog: "https://yalishizhude.github.io"} }; };

html頁面:

 <a href="https://yalishizhude.github.io">亞里士朱德</a>

當內容只想作字符串解析的時候能夠用 escapeExpression 和 SafetString 函數。

塊級helper

塊級helper獲取參數的方式跟以前差很少,只是最後多了一個參數,這個參數有兩個函數fnrevers能夠和else搭配使用。後面將會講解。

模板:

{ {#list nav} } <a href="{ {url} }">{ {title} }</a> { {/list} }

註冊helper:

Handlebars.registerHelper('list', function(context, options) { var ret = "<ul>"; for(var i=0, j=context.length; i<j; i++) { ret = ret + "<li>" + options.fn(context[i]) + "</li>"; } return ret + "</ul>"; });

數據:

{ nav: [ { url: "https://yalishihzude.github.io", title: "blog" }, { url: "https://www.github.com/yalishizhude", title: "github" }, ] }

html頁面:

<ul> <li> <a href="https://yalishizhude.github.io">blog</a> </li> <li> <a href="https://www.github.com/yalishizhude">github</a> </li> </ul>

自定義helper

each的index變量比較經常使用,可是它是從0開始的,每每不符合業務中的需求,這裏寫個helper來擴展一下。

註冊helper:

hbs.registerHelper('eval', function(str, options){ var reg = /\{\{.*?\}\}/g; var result = false; var variables = str.match(reg); var context = this; //若是是each if(options.data){ context.first = context.first||options.data.first; context.last = context.last||options.data.last; context.index = context.index||options.data.index; context.key = context.key||options.data.key; } _.each(variables, function(v){ var key = v.replace(/{ {|} }/g,""); var value = typeof context[key]==="string"?('"'+context[key]+'"'):context[key]; str = str.replace(v, value); }); try{ result = eval(str); return new hbs.handlebars.SafeString(result); }catch(e){ return new hbs.handlebars.SafeString(''); console.log(str,'--Handlerbars Helper "eval" deal with wrong expression!'); } });

模板:

{ {#each list} } { {eval '{ {index} }+1'} } { {/each} }

上面說到if不支持複雜的表達式,若是是「&&」操做還能夠用子表達式來實現,更加複雜的就很差辦了,這裏我寫了一個helper來實現。

註冊helper:

hbs.registerHelper('ex', function(str, options) { var reg = /\{\{.*?\}\}/g; var result = false; var variables = str.match(reg); var context = this; _.each(variables, function(v){ var key = v.replace(/{ {|} }/g,""); var value = typeof context[key]==="string"?('"'+context[key]+'"'):context[key]; str = str.replace(v, value); }); try{ result = eval(str); if (result) { return options.fn(this); } else { return options.inverse(this); } }catch(e){ console.log(str,'--Handlerbars Helper "ex" deal with wrong expression!'); return options.inverse(this); } });

模板:

{ {#ex "{ {state} }==='submiting'"} } <i class="icon cross-danger">1</i> { {else} } <i class="icon cross-success">2</i> { {/ex} }

先將整個邏輯表達式做爲一個字符串傳入,而後替換其中的變量值,最後用eval函數來解析表達式,同時增長異常處理。

高級玩家:partial

比較推崇使用分頁來實現組件化。分頁跟helper同樣須要先註冊。在hbs模塊中能夠批量註冊,比較簡單。

hbs.registerPartials(__dirname + '/views/partials');

基礎引用

用「>」來引用模板,這種狀況通常用來處理頁頭頁尾這種簡單的分頁。後面能夠傳入參數。 { {> myPartial param} }

當使用塊級表達式時,咱們一般添加「#」,而分頁是「>」,因此塊級分頁使用「#>」,這裏表示若是layout分頁不存在則顯示塊內的內容My Content。

{ {#> layout } } My Content { {/layout} }

動態分頁

固然也能夠用表達式來代替分頁名稱

{ {> (whichPartial) } }

當分頁中一部分代碼是固定的,另外一部分是變化的時候,能夠在分頁中添加「@partial-block」,這時當引用這個分頁時,在內部編寫代碼將會填充到這個位置。

partial.hbs:

 { {> [@partial-block](/user/partial-block) } }

模板:

{ {#>partial} } https:yalishizhude.github.io { {/partial} }

 

 

內聯分頁

當有多段代碼須要填充到分頁時,能夠用以下方法。分頁中內嵌分頁變量,模板中經過內聯分頁的方式傳入。

模板:

{ {#> partial} } { {#*inline "nav"} } 哈哈哈 { {/inline} } { {#*inline "content"} } https://yalishizhude.github.io { {/inline} } { {/partial} }

partial.hbs:

<div class="nav"> { {> nav} } </div> <div class="content"> { {> content} } </div>

html頁面:

<div class="nav"> 哈哈哈哈 </div> <div class="content"> https://yalishizhude.github.io </div>

大師級玩家:API

本文列舉的只是handlebars中最重要和經常使用的功能,更多細碎的功能能夠去查看 官方API

開頭的問題

我想將導航條寫成一個分頁(partial),導航條左邊的文字標題是能夠經過參數傳遞的,可是右邊的內容多是文字、圖片其它元素,須要具體業務自定義實現。我又不想把html代碼寫在js中,因此但願在模板中將這段未知的模板代碼填充到分頁中進行展示。我在官網文檔中找到了 {{>@partial-block}}來實現此功能,可是本機實驗一直解析報錯。 解決過程: 這個問題緣由可能有兩個,一是官方文檔有錯,二是本機環境的插件有問題(Express用hbs模塊,該模塊封裝了handlebars引擎模塊)。爲了驗證官方文檔的正確性,我找到了一個在線handlebars解析器,輸入文檔中的代碼時能夠正確解析,那麼只可能出如今hbs模塊了。這時在github上找到hbs模塊最新版本爲4,查看本地版本爲3,更新後果真能夠正常解析了。

總結

handlebars讓咱們看到一個好的插件應該有的特徵:

  • 可識別性。接口簡單,使用方便,容易上手。
  • 高可用性。自帶經常使用一些功能(helper),不求多而求精。
  • 可擴展性。複雜的業務邏輯,開發人員能夠自定義helper去擴展和實現。
相關文章
相關標籤/搜索