为什么需要模板引擎

关于前端的模板引擎,我用一个公式来解释

  1. 模板引擎
  2. 模板 + 数据 ========> html页面

模板引擎就像是html的解析生成器,将对应的模板填充完数据之后生成静态的html页面。它可以在浏览器端(比如angular中指令所用的模板)也可以在服务器端执行,不过一般用于服务器端。因为它的一个作用是抽象公共页面来重用,如果在服务端填充数据,可以减少回填数据给页面的ajax请求,从而提升浏览器端整体页面渲染速度。

那些年我用过的模板引擎

接触过的模板引擎不算多,最早应该是jsp,本质上也是一种模板引擎,再到功能稍微强大的freemarker,这两种都是属于java语系的。js语系的jade和ejs我都有所接触,不过不常用,jade那种类python的语法规则以及较低的解析效率都让我不敢兴趣,Express框架也只是早起将其作为模板引擎。后来换成了强大的ejs,无论是功能还是写法上都接近jsp了。直到最新的Express4发布,默认改为了弱逻辑的比较简洁的模板引擎handlebars。

我使用handlebars有以下几个原因:

  • 这次新项目前端框架搭建基于Express4,模板引擎只能在ejs/jade/hogan/hbs中选择一个。
  • 默认是handlebars,虽不知道原因,想必有其原因。
  • 看过“去哪儿”的前端技术分享,他们就是在handlebars上进行封装的,证明已经有人填过坑了,可以一试。
  • 开始比较看好ejs,但是官网文档被强了,相比之下handlebars的文档比较清晰,还有实例,虽然逻辑结构比较混乱,但是基本无障碍。

码解handlebars

运行环境:Express4、hbs4 未接触Express或hbs的可以先看这里

初级玩家:表达式

数据:

  1. {
  2. title: 'Express',
  3. obj:{
  4. version: 'v4.3',
  5. category: 'node',
  6. "date~": '2016'
  7. }
  8. }

模板:

  1. <p>{ {title} }</p>
  2. <p>{ {obj.version} }</p>
  3. <p>{ {obj/category} }</p>
  4. <p>{ {obj.date~} }</p>

html页面:

  1. Express
  2. v4.3
  3. node

handlebars中变量都添加双花括号来表示(类似Angular),对比ejs的"<%%>“来说看起来没什么区别,其实这是很人性化的,想一下你键盘上的位置,再考虑按这几个字符的难易程度你就懂了。 其中要访问变量的属性值时可以用类似json格式的”.",也可以用"/"。

其中变量名不可包含以下字符。如果包含则不被解析,如上的"{{obj.date~}}"。

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

但可以用 " , ’ , [] 来转译这些特殊字符。

这一条规则意味着 “&&”,"||","!"这类逻辑判断是不能出现在表达式中的! (看着这一条是不是觉得弱爆了,要不然怎么叫若逻辑模板引擎呢~哈哈,不过当然有另外的解决办法)。

中级玩家:helper

英语水平有限,实在找不到一个恰当的词来翻译它了。可以理解为它是注入到模板中的一个函数,用来接收参数并进行逻辑处理。

默认helper

if else

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

和一般的编程语言的 if-else 代码块是差不多的,不过再次重申由于上面提到的特殊字符,所以if条件中是不能有逻辑表达式的,只能是变量或者值。

unless

还是因为上面提到的那些字符,handlebars不支持逻辑非("!"),所以又有了一个与if相反的helper

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

上面这段代码就等价于

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

each

都知道each相当于for循环。不过有些地方需要注意:

  • 可以用相对路径的方式来获取上一层的上下文。(上下文概念跟js中的上下文差不多,比如在each passage代码块内,每一次循环上下文一次是passage[0],passage[1]…)
  • 一些默认变量,@first/@last 当该对象为数组中第一个/最后一个时返回真值。如果数组成员为值而非对象,@index表示当前索引值,可以用@key或者this获取当前值
  • 可以用 as |xxx|的形式给变量起别名,循环中通过别名可以引用父级变量值。当然也可以通过相对路径的方式引用父级变量。
  1. { {#each passage} }
  2. { {#each paragraphs} }
  3. { {@../index} }:{ {@index} }:{ {this} }</p>
  4. { {else} }
  5. <p class="empty">No content</p>
  6. { {/each} }
  7. { {/each} }
  1. { {#each array as |value, key|} }
  2. { {#each child as |childValue, childKey|} }
  3. { {key} } - { {childKey} }. { {childValue} }
  4. { {/each} }
  5. { {/each} }

同时也可以用来遍历对象,这时@key表示属性名,this表示对应的值

  1. { {#each object} }
  2. { {@key} }: { {this} }
  3. { {/each} }

with

类似js中的with,可以配合分页使用,限定作用域。

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

lookup

这个用于以下这种并列数组的情况,可以按照索引来找兄弟变量对应的值。理解起来有些困难,直接看代码

  1. {
  2. groups: [
  3. {id: 1, title: "group1"},
  4. {id: 2, title: "group2"},
  5. ],
  6. users: [
  7. {id:1, login: "user1", groupId: 1},
  8. {id:2, login: "user2", groupId: 2},
  9. {id:3, login: "user3", groupId: 1}
  10. ],
  11. infos: [
  12. 'a','b','c'
  13. ]
  14. }
  1. <table>
  2. { {#each users} }
  3. <tr data-id="{ {id} }">
  4. <td>{ {login} }</td>
  5. <td data-id="{ {groupId} }">{ {lookup ../infos @index} }</td>
  6. </tr>
  7. { {/each} }
  8. </table>
  1. user1 a
  2. user2 b
  3. user3 c

这里在users数组中按照索引值引用infos数组中对应的值,如果想引用groups中的groupId呢?很简单,用with。

  1. <table>
  2. { {#each users} }
  3. <tr data-id="{ {id} }">
  4. <td>{ {login} }</td>
  5. <td data-id="{ {groupId} }">{ {#with (lookup ../groups @index)} }{ {title} }{ {/with} }</td>
  6. </tr>
  7. { {/each} }
  8. </table>

自定义helper

内置的helper不够强大,所以通常需要写js代码自定义helper,先看一个简单的单行helper。

行级helper

传值

数值、字符串、布尔值这种常规数据可以直接传入,同时也可以传递JSON对象(但只能传一个),以key=value这种形式写在后面,最后就可以通过参数的hash属性来访问了。

模板

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

代码

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

传变量时可以用this指针来指代它访问属性,通过逻辑判断后可以返回一段html代码,不过太建议这样做。考虑以后的维护性,这种html代码和js代码混合起来的维护性是比较差的,如果要抽象层组件还是使用分页比较好。

模板:

  1. { {agree_button person} }

注册helper:

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

数据:

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

html页面:

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

当内容只想做字符串解析的时候可以用 escapeExpression 和 SafetString 函数。

块级helper

块级helper获取参数的方式跟之前差不多,只是最后多了一个参数,这个参数有两个函数fnrevers可以和else搭配使用。后面将会讲解。

模板:

  1. { {#list nav} }
  2. <a href="{ {url} }">{ {title} }</a>
  3. { {/list} }

注册helper:

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

数据:

  1. {
  2. nav: [
  3. { url: "https://yalishihzude.github.io", title: "blog" },
  4. { url: "https://www.github.com/yalishizhude", title: "github" },
  5. ]
  6. }

html页面:

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

自定义helper

each的index变量比较常用,但是它是从0开始的,往往不符合业务中的需求,这里写个helper来扩展一下。

注册helper:

  1. hbs.registerHelper('eval', function(str, options){
  2. var reg = /\{\{.*?\}\}/g;
  3. var result = false;
  4. var variables = str.match(reg);
  5. var context = this;
  6. //如果是each
  7. if(options.data){
  8. context.first = context.first||options.data.first;
  9. context.last = context.last||options.data.last;
  10. context.index = context.index||options.data.index;
  11. context.key = context.key||options.data.key;
  12. }
  13. _.each(variables, function(v){
  14. var key = v.replace(/{ {|} }/g,"");
  15. var value = typeof context[key]==="string"?('"'+context[key]+'"'):context[key];
  16. str = str.replace(v, value);
  17. });
  18. try{
  19. result = eval(str);
  20. return new hbs.handlebars.SafeString(result);
  21. }catch(e){
  22. return new hbs.handlebars.SafeString('');
  23. console.log(str,'--Handlerbars Helper "eval" deal with wrong expression!');
  24. }
  25. });

模板:

  1. { {#each list} }
  2. { {eval '{ {index} }+1'} }
  3. { {/each} }

上面说到if不支持复杂的表达式,如果是“&&”操作还可以用子表达式来实现,更加复杂的就不好办了,这里我写了一个helper来实现。

注册helper:

  1. hbs.registerHelper('ex', function(str, options) {
  2. var reg = /\{\{.*?\}\}/g;
  3. var result = false;
  4. var variables = str.match(reg);
  5. var context = this;
  6. _.each(variables, function(v){
  7. var key = v.replace(/{ {|} }/g,"");
  8. var value = typeof context[key]==="string"?('"'+context[key]+'"'):context[key];
  9. str = str.replace(v, value);
  10. });
  11. try{
  12. result = eval(str);
  13. if (result) {
  14. return options.fn(this);
  15. } else {
  16. return options.inverse(this);
  17. }
  18. }catch(e){
  19. console.log(str,'--Handlerbars Helper "ex" deal with wrong expression!');
  20. return options.inverse(this);
  21. }
  22. });

模板:

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

先将整个逻辑表达式作为一个字符串传入,然后替换其中的变量值,最后用eval函数来解析表达式,同时增加异常处理。

高级玩家:partial

比较推崇使用分页来实现组件化。分页跟helper一样需要先注册。在hbs模块中可以批量注册,比较简单。

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

基础引用

用“>”来引用模板,这种情况一般用来处理页头页尾这种简单的分页。后面可以传入参数。 { {> myPartial param} }

当使用块级表达式时,我们通常添加“#”,而分页是“>”,所以块级分页使用“#>”,这里表示如果layout分页不存在则显示块内的内容My Content。

  1. { {#> layout } }
  2. My Content
  3. { {/layout} }

动态分页

当然也可以用表达式来代替分页名称

{ {> (whichPartial) } }

当分页中一部分代码是固定的,另一部分是变化的时候,可以在分页中添加“@partial-block”,这时当引用这个分页时,在内部编写代码将会填充到这个位置。

partial.hbs:


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

模板:

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

内联分页

当有多段代码需要填充到分页时,可以用如下方法。分页中内嵌分页变量,模板中通过内联分页的方式传入。

模板:

  1. { {#> partial} }
  2. { {#*inline "nav"} }
  3. 哈哈哈
  4. { {/inline} }
  5. { {#*inline "content"} }
  6. https://yalishizhude.github.io
  7. { {/inline} }
  8. { {/partial} }

partial.hbs:

  1. <div class="nav">
  2. { {> nav} }
  3. </div>
  4. <div class="content">
  5. { {> content} }
  6. </div>

html页面:

  1. <div class="nav">
  2. 哈哈哈哈
  3. </div>
  4. <div class="content">
  5. https://yalishizhude.github.io
  6. </div>

大师级玩家:API

本文列举的只是handlebars中最重要和常用的功能,更多细碎的功能可以去查看 官方API

开头的问题

我想将导航条写成一个分页(partial),导航条左边的文字标题是可以通过参数传递的,但是右边的内容可能是文字、图片其它元素,需要具体业务自定义实现。我又不想把html代码写在js中,所以希望在模板中将这段未知的模板代码填充到分页中进行展现。我在官网文档中找到了 {{>@partial-block}}来实现此功能,但是本机实验一直解析报错。 解决过程: 这个问题原因可能有两个,一是官方文档有错,二是本机环境的插件有问题(Express用hbs模块,该模块封装了handlebars引擎模块)。为了验证官方文档的正确性,我找到了一个在线handlebars解析器,输入文档中的代码时可以正确解析,那么只可能出现在hbs模块了。这时在github上找到hbs模块最新版本为4,查看本地版本为3,更新后果然可以正常解析了。

总结

handlebars让我们看到一个好的插件应该有的特征:

  • 可识别性。接口简单,使用方便,容易上手。
  • 高可用性。自带常用一些功能(helper),不求多而求精。
  • 可扩展性。复杂的业务逻辑,开发人员可以自定义helper去扩展和实现。

handlebars用法的更多相关文章

  1. 第07组 Alpha冲刺(2/6)

    队长:摇光 队长:杨明哲 组长博客:求戳 作业博客:求再戳 队长:杨明哲 过去两天完成了哪些任务 文字/口头描述:重写后端,完成了数据请求部分的后端. 展示GitHub当日代码/文档签入记录:(组内共 ...

  2. js模版引擎handlebars.js实用教程——if-判断的基本用法

    返回目录 <!DOCTYPE html> <html> <head> <META http-equiv=Content-Type content=" ...

  3. js模版引擎handlebars.js实用教程——另一种Helper用法

    返回目录 <!DOCTYPE html> <html> <head> <META http-equiv=Content-Type content=" ...

  4. 关于模板引擎handlebars.js基本用法

    说明:模板引擎主要针对于渲染DOM,取代了字符串拼接,用下面的代码亲测handlebars模板引擎比字符串拼接渲染DOM慢了20ms, 这里配置一个在线DEMO,简单说明下handlebars.js的 ...

  5. handlebars Helper用法

    handlebars  Helper用法:  http://www.cnblogs.com/iyangyuan/archive/2013/12/12/3471357.html 逻辑运算符在handle ...

  6. Handlebars的基本用法 Handlebars.js使用介绍 http://handlebarsjs.com/ Handlebars.js 模板引擎 javascript/jquery模板引擎——Handlebars初体验 handlebars.js 入门(1) 作为一名前端的你,必须掌握的模板引擎:Handlebars 前端数据模板handlebars与jquery整

    Handlebars的基本用法 使用Handlebars,你可以轻松创建语义化模板,Mustache模板和Handlebars是兼容的,所以你可以将Mustache导入Handlebars以使用 Ha ...

  7. Handlebars 模板引擎之前后端用法

    前言 不知不觉间,居然已经这么久没有写博客了,坚持还真是世界上最难的事情啊. 不过我最近也没闲着,辞工换工.恋爱失恋.深圳北京都经历了一番,这有起有落的生活实在是太刺激了,就如拿着两把菜刀剁洋葱一样, ...

  8. Handlebars的基本用法

    使用Handlebars,你可以轻松创建语义化模板,Mustache模板和Handlebars是兼容的,所以你可以将Mustache导入Handlebars以使用 Handlebars 强大的功能. ...

  9. js模板引擎之 Handlebars 基本用法

    模板引擎比较久远的一个技术,常见的模板引擎有 baiduTemplate(百度)\artTemplate(腾讯)\juicer(淘宝)\doT\ tmpl\ handlebars\ easyTempl ...

随机推荐

  1. C#正则表达式快速入门

    作者将自己在学习正则表达式中的心得和笔记作了个总结性文章,希望对初学C#正则表达式的读者有帮助. [内容] 什么是正则表达式 涉及的基本的类 正则表达式基础知识 构建表达式基本方法 编写一个检验程序 ...

  2. 《算法竞赛进阶指南》1.4Hash

    137. 雪花雪花雪花 有N片雪花,每片雪花由六个角组成,每个角都有长度. 第i片雪花六个角的长度从某个角开始顺时针依次记为ai,1,ai,2,-,ai,6. 因为雪花的形状是封闭的环形,所以从任何一 ...

  3. Spring学习和应用

    Java EE轻量级框架. 核心:反转控制(IOC),依赖注入. 功能:增删改查bean,   功能: 1.容器功能:    代替了EJB容器,负责管理用户基于POJO方式写的业务逻辑组件,具有类似E ...

  4. 简述raid0,raid1,raid5,raid10 的工作原理及特点

    RAID 0 支持1块盘到多块盘,容量是所有盘之和 RAID1 只支持2块盘,容量损失一块盘 RAID 5最少三块盘,不管硬盘数量多少,只损失一块容量 RAID 10最少4块盘,必须偶数硬盘,不管硬盘 ...

  5. 源码分析ConcurrentHashMap

    ConcurrentHashMap 1.7 segment分段锁 1.8 CAS 红黑树

  6. GHOST来进行备份和还原及菜单介绍

    这篇文章主要说的是如何手工操作GHOST来进行备份和还原. GHOST的菜单及功能: 在主菜单中,有以下几项: l Local:本地操作,对本地计算机上的硬盘进行操作. l Peer to peer: ...

  7. JadClipse

    JadClipse 介绍:JadClipse是一个Eclipse插件,无缝地集成了Jad(快速Java反编译器)与Eclipse.通常,当打开一个类文件,类文件查看器将显示一个简短的API类的轮廓.如 ...

  8. Java local 转UTC时间

    import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; impor ...

  9. bash:haoop:command not found

    今天重新搭建了一个3节点的Hadoop集群,想着在上面测试一个MapReduce实例,然后就出现了以下错误: [hadoop@master hadoop-]$ hadoop -bash: hadoop ...

  10. Spring Security方法级别授权使用介绍

    1.简介 简而言之,Spring Security支持方法级别的授权语义. 通常,我们可以通过限制哪些角色能够执行特定方法来保护我们的服务层 - 并使用专用的方法级安全测试支持对其进行测试. 在本文中 ...