Underscore 模板引擎 API 更新 #4

Open
cssmagic opened this Issue Jun 16, 2013 · 9 comments

Comments

Projects
None yet
4 participants
Owner

cssmagic commented Jun 16, 2013 edited

Underscore 模板引擎 API 更新

语法

Underscore 的模板引擎 _.template() 脱胎于 jQuery 作者的作品 Micro-Templating。但从 Underscore 1.3.3 开始,这个方法做了较大的调整,在保留旧语法的基础上,还新增支持了一个 {variable: 'foo'} 对象作为第三个参数。

一旦传入了这个参数,则模板(第一个参数)中的变量将不再指向待渲染数据(第二个参数)的属性,模板中的 foo 变量将直接指向待渲染数据(第二个参数)自身

比如,原来的调用方法:

_.template('I love <%= person %>.', {person: 'you'});

在新语法下可以写为:

_.template('I love <%= foo.person %>.', {person: 'you'}, {variable: 'foo'});

比较

看起来似乎变复杂了,但这样做有两个好处:

内部实现

旧语法在实现上,需要使用 with 声明来实现对数据对象属性的查找。with 有哪些问题,这里就不多说了。而新语法由于已经在模板中指定了数据对象自身,则不需要用 with 来搜索其属性。据官方文档称,“模板渲染性能得到极大提升”。

外部接口

新语法带来了一个隐性的改良——接口灵活性更高,即待渲染数据(第二个参数)可以不仅是对象,也可以是数组等其它数据类型。仍然以上面的代码为例,用新语法还可以写成这样:

_.template('I love <%= foo %>.', 'you', {variable: 'foo'});

这样传入的数据更自由,数组等数据不需要被包装为对象再传入了。

使用

在实际应用中,往往存在某个常用的模板需要被多次渲染的情况。此时,为优化性能,我们通常会采用“两步渲染法”——先把模板编译成模板函数备用;按需执行已经编译好的模板函数,把不同的数据渲染为不同的结果——以避免同一模板的重复编译。如下所示:

var fnRender = _.template('I love <%= person %>.');
fnRender({person: 'you'});  //'I love you.'
fnRender({person: 'her'});  //'I love her.'

在这种情况下,我们无法使用 {variable: 'foo'} 参数。那怎么办呢?

幸好有 _.templateSettings 可以进行 _.template() 的全局设置:

_.extend(_.templateSettings, {variable: 'foo'});

在此之后编译的所有模板函数即工作在新语法之下。需要注意的是,这个设置是全局的,也就是说,当前页面的所有模板和相关功能都需要以新语法来写,并将 foo 统一命名。


更新

从 Underscore 1.7 开始,这个 API 的行为又发生了一些变化,我们有必要再来看一看。

从这个版本开始,_.template() 方法将不再接受模板数据了,它的返回值就总是编译生成的模板函数了。也就是说原先一步渲染模板的用法需要修改成两步走:

// before
_.template('I love <%= person %>.', {person: 'you'});

// after
_.template('I love <%= person %>.')({person: 'you'});

看起来很蛋疼?无关痛痒?其实 Underscore 下决心引入这个 “破坏性变更” 还是很有深意的。我认为这个改动的好处在于:

  • 消灭了返回值的不确定性,令这个 API 的行为更易于理解。
  • 去掉一步到位的用法,虽然牺牲了眼前的便利,但同时也强制使用者了解模板引擎的基本原理,一定程度上会推动使用者考虑模板缓存,进而提升应用的整体性能。

© Creative Commons BY-NC-ND 4.0   |   我要订阅   |   我要打赏

edokeh commented Jun 16, 2013

汗,1.4 都用了好久了,还真没注意到有这个更新,这么个设置还是挺有用的
主要是项目里全换成 handlebars 了,underscore 的模板还是有点弱啊

Owner

cssmagic commented Jun 16, 2013

@edokeh
我的都是小项目,本来就依赖 Underscore,模板引擎也直接用它的了。简单够用,更复杂的估计我也不会用,呵呵。
不过等等,Underscore 的模板支持完整的 JS 逻辑,开放、灵活,可以说它简单,但并不“弱”吧?

edokeh commented Jun 17, 2013

嘿嘿,我们也是小项目啊,只是碰到些奇怪的需求嘛
主要遇到的问题是,underscore 缺少一个 helper 层,导致某些功能实现起来很别扭
我们的前端模板里面某些文字要做 i18n ,用 underscore 的话就只能污染数据对象来实现了

比如模板是这样

<div>
  <h1><%= i18n('title') %>:<%= name %></h1>
</div>

那么就得这么弄

var data = {name:'edokeh'};

_.extend(data, {
  i18n: function(key) {
    return I18N_DATA[key]
  }
})

_.template('...', data);

这时候 data 对象就被污染了,可能会有些潜在问题,而且从逻辑上来说也很不好啊

Owner

cssmagic commented Jun 17, 2013

@edokeh
Helper 层完全可以自己写,我感觉反而更灵活(借口,实际上模板规则太多了我记不住啊,哈哈)。Underscore 模板内部可以调用任何全局作用域可以调用的函数和变量,因为模板内的所有逻辑代码就是原生的 JavaScript 代码。

比如你的例子,其实不需要扩展数据源,直接这样写模板就可以:

<div>
  <h1><%= I18N_DATA['title'] %>:<%= name %></h1>
</div>

如果一定解除模板对全局变量的依赖,并且不想改写数据源,也可以为模板单独准备数据:

var data = {name: 'edokeh'};
var dataToRender = _.extend({}, data, {
  title: I18N_DATA['title']
});

然后使用这个模板:

<div>
  <h1><%= title %>:<%= name %></h1>
</div>

当然这样稍显琐碎,还是前一种方法更直观。我感觉模板里存在适度逻辑(或对全局变量的适度调用)还是 OK 的。

edokeh commented Jun 17, 2013

我们用 SeaJS 封装的模块,没有全局变量。。。
那个扩展的方式其实我们之前采用过的,可是写起来真的好麻烦。。。索性整体换个模板系统好了
其实 handlebars 用起来也不是很爽,里面完全不能放逻辑,不过幸好我们的系统没那么复杂,基本都能应对
赞同你说的适度逻辑的观点,handlebars 的世界观有些过于激进了

Owner

cssmagic commented Jun 17, 2013

@edokeh 是这样啊,明白了。
p.s. 不过 Sea.js 模块应该也可以手工暴露全局变量的,嘿嘿。

是否有哪个线上应用用到了这个!

Owner

cssmagic commented Apr 10, 2014

@godpeacy 不好意思现在才看到你的留言。

我自己的经验是这样的:


更新:

cssmagic referenced this issue in cssmagic/underscore-template Jan 19, 2015

Open

API 文档 #5

underscore is a greate template engine for html render.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment