美文网首页🐋成员文章
Handlebars学习记录

Handlebars学习记录

作者: 烈风裘 | 来源:发表于2017-08-24 23:37 被阅读101次

    学习缘由

    Handlebars模板引擎从去年就关注了,因为使用类HTML格式/类Vue/类Angular的语法我比较习惯,所以就定在了学习计划中。目前公司后台渲染项目都是使用Handlebars模板引擎,所以这篇博文简单做下学习记录。

    模板引擎是做什么的

    模板引擎所做的工作简单的说就是:

    输入模板字符串 + 数据 => 得到渲染过的字符串

    需要重申的是,得到的是经过数据填充的静态字符串。此外,根据页面的结构再拼接其余部分HTML片段,最后在发送到客户端。因此,在需要SEO场景条件下比较合适,比如:社交、新闻、博客、站点导航等网站是必须使用的。

    此外,模板引擎除了Handlebarsjs还有:Jade templatingUnderscore TemplatesMustache等。除非现有的各类模板引擎不适合自己的业务,建议在选择开源项目的时候还是选择口碑好一些、目前还在维护的项目。

    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*、组件树、组件/模块、数据驱动视图)思路去做。

    参考

    相关文章

      网友评论

        本文标题:Handlebars学习记录

        本文链接:https://www.haomeiwen.com/subject/krdedxtx.html