学习缘由
Handlebars模板引擎从去年就关注了,因为使用类HTML格式/类Vue/类Angular的语法我比较习惯,所以就定在了学习计划中。目前公司后台渲染项目都是使用Handlebars模板引擎,所以这篇博文简单做下学习记录。
模板引擎是做什么的
模板引擎所做的工作简单的说就是:
输入模板字符串 + 数据 => 得到渲染过的字符串。
需要重申的是,得到的是经过数据填充的静态字符串。此外,根据页面的结构再拼接其余部分HTML片段,最后在发送到客户端。因此,在需要SEO场景条件下比较合适,比如:社交、新闻、博客、站点导航等网站是必须使用的。
此外,模板引擎除了Handlebarsjs还有:Jade templating、Underscore Templates、Mustache等。除非现有的各类模板引擎不适合自己的业务,建议在选择开源项目的时候还是选择口碑好一些、目前还在维护的项目。
Basic Usage
一、JS模板编译
可以将模板字符串写在*.hbs
中,也可以是在<script>
中定义
二、插值
语法和Angular、Vue很像:{{value}}
、{{{Html}}}
。
-
{{value}}
:在当前的上下文中找value这个属性并将值填充到这个位置。其实,这也可以理解为一个内置helper
,意思是查找value
这个名称的helper
。 -
{{{Html}}}
:同上,但是内容原样输出不转义。
这里需要注意在传入的变量中不能有特殊字符:
! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~
如果有的话需要用[]
包起来(也可以是'
或者"
):
{{#each articles.[10].[#comments]}}
<h1>{{subject}}</h1>
<div>
{{body}}
</div>
{{/each}}
三、Helpers
Helper使用前需要注册,一般用于对数据的再加工,比如:
- 改变数据格式
- 给数据外边套一些Markup片段
- Markup片段保存/传送(style/script)
1. 单行 Helper
单行的Helper可以是双大括号{{xx}}
,或者是{{{xx}}}
包裹,区别在于:
-
{{xx}}
:内部返回的模板字符串会转义处理,标签显示为字符串 -
{{{xx}}}
:和上面的插值处理方式一样,返回的字符串不转义
{{{name data1 data2 hashKey1=hashValue1 hashKey2=hashValue2}}}
参数(格式固定)
参数含义有位置和书写格式确定,使用空格分离,分为三类:
- name:helper的名称
- data1/data2:传入helper的数据,可以是多个,写在data后面空格隔开就行
- hash:options对应最后参数,以
key=value
形式书写,这个在helper定义的option.hash拿到,如果未定义则为{}
,不需要为空判断。
注册Helper
Handlebars.registerHelper('name', function(data1, data2, options) {
// options.name: 当前helper的名称, 例如: debug
// options.hash[hashKey1] === hashValue1
// options.hash[hashKey2] === hashValue2
// options.fn(this): 块级helper内部的模板函数
// options.inverse: else分支的模板函数
// options.data.root: 当前页面填充的数据对象
// return 的字符串就是渲染的Markup片段
});
示例
{{{link "See more..." href=story.url class="story"}}}
Handlebars.registerHelper('link', function(text, options) {
var attrs = [];
for (var prop in options.hash) {
attrs.push(
Handlebars.escapeExpression(prop) + '="'
+ Handlebars.escapeExpression(options.hash[prop]) + '"');
}
return new Handlebars.SafeString(
"<a " + attrs.join(" ") + ">" + Handlebars.escapeExpression(text) + "</a>"
);
});
2. 块级 Helper
带有内部模板结构的写法,参数和注册方法同上,具体写法如下:
{{#name data1 data2 hashKey1=hashValue1 hashKey2=hashValue2}}
内部模板
{{/name}}
示例
{{#bold}}{{body}}{{/bold}}
Handlebars.registerHelper('bold', function(options) {
return new Handlebars.SafeString(
'<div class="mybold">'
+ options.fn(this)
+ '</div>');
});
3. 输出原始 Blocks
书写原始的mustache
语句块:
示例
// 模板
{{{{raw-helper}}}}
{{bar}}
{{{{/raw-helper}}}}
// 对应的Helper函数
Handlebars.registerHelper('raw-helper', function(options) {
return options.fn();
});
// 结果
{{bar}}
4. 已经注册好的常用Helper:
if/else/unless/with/each/log/blockHelperMissing/helperMissing
5. 嵌入子Helper(Subexpressions)
意思是,在父Helper中传入子Helper的模板String,语法是这样的:
{{outer-helper (inner-helper 'abc') 'def'}}
- 内部使用名称为
inner-helper
的helper,传入参数:abc
- 外部使用名称为
outer-helper
的helper,传入参数:生成的模板字符串
,def
6. 删除多余换行符(Inline)
正常书写block是有换行符的,Inline模式可以将换行符都删除。注意加~
的位置!
示例
// 模板
{{#each nav ~}}
<a href="{{url}}">
{{~#if test}}
{{~title}}
{{~^~}}
Empty
{{~/if~}}
</a>
{{~/each}}
// 数据
{
nav: [
{url: 'foo', test: true, title: 'bar'},
{url: 'bar'}
]
}
// 结果
<a href="foo">bar</a><a href="bar">Empty</a>
7. 输出原始数据
是的,原封不动的输出,有两种方式:
- 单行模式,以
\
开头
\这里填写你要输出的原始String
- block模式,raw变量(true\false)控制是否显示内部内容
{{{{raw}}}}
这里填写你要输出的原始String
{{{{/raw}}}}
8. each Helper的as语法
遍历的同时提取value-key,便于内部使用,下面的例子一目了然。
{{#each array as |value key|}}
{{#each child as |childValue childKey|}}
{{key}} - {{childKey}}. {{childValue}}
{{/each}}
{{/each}}
四、Partials
这个可以理解为子模板,用于模板复用,子模板继承当前的上下文变量环境。使用前需要注册,这个和Helper一致。
Handlebars.registerPartial('myPartial', '{{name}}')
- 模板中的name可以理解为为
this.name
- 第二个参数必须是字符串(有些不友好)
Partials使用场景包括:Footer(友情链接)、Header(Logo登录)、Nav(导航)等。
1. 单行模板
{{> Partial名称 key1=value2}}
- 注册的Partials将会填充在这个位置
- 支持传入参数,参数可在内部直接使用(和Helper完全不同,不影响上下文this)
- 注意!没有三个括号的写法(这个和Helper完全不同)
动态模板
通过chooseHelper
这个Helper帮忙选择使用哪个Partial,chooseHelper
应该返回已注册的Partial名称
。
括号中使用Helper的语法:
{{> (chooseHelper) }}
2. 块级模板
块级需要加#标。如果使用Vue,这部分和slot的概念很像。这里分三类:
fallback方案
{{#> myPartial }}
Failover content(如果myPartial未定义显示这里的东西)
{{/myPartial}}
内部通过@partial-block获取外部block内全部模板
// 模板内容
{{#> layout }}
<p> layout中的默认全部slot</p>
{{/layout}}
// layout模板内容
{{> @partial-block }}
// 结果
<p> layout中的默认全部slot</p>
将子模板嵌入具体位置(具有名字的slot方案)
这个概念和Vue的具名Slot概念一致,直接贴代码,简单来说:各回各家,各找各妈。
// 模板
{{#> layout}}
{{#*inline "nav"}}
My Nav
{{/inline}}
{{#*inline "content"}}
My Content
{{/inline}}
{{/layout}}
// layout模板内容
<div class="nav">
{{> nav}}
</div>
<div class="content">
{{> content}}
</div>
// 结果
<div class="nav">
My Nav
</div>
<div class="content">
My Content
</div>
五、注释
{{!-- 这段注释只在源码中显示 --}}
{{! 这段注释只在源码中显示 }}
<!-- 这段注释会在最终的HTML中显示 -->
六、APIs
Handlerbar提供的API分三类:
- Base:包括模板编译、注册/注销各类Helper/Partial、模板转移等。
- 工具类:判断变量类型
- 数据变量:@root、@first、@last、@key等
需要说明的API:
Handlebars.escapeExpression(string)
字符转义函数,将string中的 &, <, >, ", ', `, =
变为转义字符. 保证传入的字符串是安全的。例如:
hbs.Utils.escapeExpression('<script>alert(1)</script>')
// 转化为
<script>alert(1)</script>
// 在html中显示字符串
<script>alert(1)</script>
如果上面具有攻击性的字符串未转义,将在浏览器中弹出alert对话框。
new Handlebars.SafeString(string)
标记内容是安全的,单独使用并不起什么作用:
new hbs.SafeString('<script>alert(1)</script>')
// 返回
SafeString { string: '<script>alert(1)</script>' }
需要和上面的escapeExpression
组合使用:
Handlebars.registerHelper('link', function(text, url) {
text = Handlebars.Utils.escapeExpression(text);
url = Handlebars.Utils.escapeExpression(url);
var result = '<a href="' + url + '">' + text + '</a>';
return new Handlebars.SafeString(result);
});
模板渲染过程
在Express中默认使用的``hbs```模块渲染Handlerbars模板,渲染过程是这样子的:
- 查找路由对应的模板,进行编译,并将编译的结果保存为 A
- 查找layout.hbs,如果
- 存在:对layout.hbs进行编译,其中{{{body}}}标签替换成 A,并返回最终编译结果B
- 不存在:返回A
知识图谱
Handlerbars结构.png使用建议
- 前端使用了自定义的Helper/Partial需要和后端同步,不要忘记。
- 如果是静态内容为主,那就直接服务端渲染好了,首屏加载速度快,不建议使用复杂的Helper/Partial,View层专注展示即可,数据格式在填充模板前处理好。非主要部分使用异步加载,比如简书的投稿管理/评论/推荐阅读使用的是Vue做异步内容渲染的。
- 如果是数据交互、动态的应用界面(比如中后台系统、查询系统),那就不应该用拼模板的思路去做,而是用做应用的架构(MV*、组件树、组件/模块、数据驱动视图)思路去做。
网友评论