最近,我遇到了据称是Logic-less template 的mustache。
但是,没有解释为什么它是以无逻辑方式设计的。换句话说,无逻辑模板的优势是什么?
换句话说,它可以防止你射中自己的脚。在过去的 JSP 时代,JSP 文件中散布着 Java 代码是很常见的,这使得重构变得更加困难,因为您的代码是分散的。
如果您通过设计阻止了模板中的逻辑(就像 mustache 一样),您将不得不将逻辑放在其他地方,因此您的模板最终会变得整洁。
另一个优点是您被迫考虑关注点分离:您的控制器或逻辑代码必须在将数据发送到 UI 之前进行数据按摩。如果您稍后将您的模板切换到另一个模板(假设您开始使用不同的模板引擎),转换会很容易,因为您只需要实现 UI 细节(因为模板上没有逻辑,请记住)。
我觉得在我看来我几乎是孤独的,但我坚定地站在对立的阵营。我不认为模板中可能混合的业务逻辑足以成为不使用编程语言的全部功能的理由。
无逻辑模板的常见论点是,如果您可以完全访问您的编程语言,您可能会混合在模板中没有位置的逻辑。我发现这类似于你应该用勺子切肉的推理,因为如果你用刀子可能会割伤自己。这是非常正确的,但是如果你使用后者,你会更有效率,尽管要小心。
例如,考虑以下使用mustache的模板片段:
{{name}}:
<ul>
{{#items}}
<li>{{.}}</li>
{{/items}}
</ul>
我可以理解这一点,但我发现以下(使用下划线)更简单直接:
<%- name %>:
<ul>
<% _.each(items, function(i){ %>
<li><%- i %></li>
<% }); %>
</ul>
话虽如此,我确实理解无逻辑模板具有优势(例如,它们可以与多种编程语言一起使用而无需更改)。我认为这些其他优势非常重要。我只是不认为他们没有逻辑的天性是其中之一。
小胡子没有逻辑?
这不就是:
{{#x}}
foo
{{/x}}
{{^x}}
bar
{{/x}}
和这个很像?
if x
"foo"
else
"bar"
end
这不是非常类似于(阅读:几乎是一个定义)表示逻辑吗?
无逻辑模板是包含供您填充的孔的模板,而不是您如何填充它们的模板。逻辑放置在别处并直接映射到模板。这种关注点分离是理想的,因为这样模板可以很容易地用不同的逻辑构建,甚至可以用不同的编程语言构建。
从小胡子手册:
我们称其为“无逻辑”,因为没有 if 语句、else 子句或 for 循环。相反,只有标签。一些标签被替换为一个值,一些什么都没有,而另一些则是一系列值。本文档解释了不同类型的 Mustache 标签。
硬币的另一面是,在不顾一切地将业务逻辑排除在表示之外的尝试中,您最终将大量表示逻辑放入模型中。一个常见的示例可能是您希望将“奇数”和“偶数”类放在表中的交替行上,这可以通过视图模板中的简单模运算符来完成。但是,如果您的视图模板不允许您这样做,那么在您的模型数据中,您不仅要存储哪一行是奇数还是偶数,而且取决于您的模板引擎的限制程度,您甚至可能需要污染您的模型使用实际 CSS 类的名称。视图应该与模型分开,句号。但是模型也应该与视图无关,这就是许多这些“无逻辑”模板引擎让你忘记的地方。逻辑在两个地方都有,实际上确实可以正确决定它的去向。是演示问题还是业务/数据问题?为了获得 100% 的原始景观,污染只是落在了另一个不太明显但同样不合适的地方。
向另一个方向发展的运动越来越多,希望事情会集中在更合理的中间地带。
它使您的模板更清晰,并迫使您将逻辑保留在可以正确进行单元测试的地方。
这种谈话感觉就像是中世纪的僧侣们争论一根针的末端可以容纳多少天使。换句话说,它开始感到虔诚、徒劳和注意力不集中。
随之而来的是小型咆哮(请随意忽略):
如果您不想继续阅读.. 我对上述主题的简短回应是:我不同意无逻辑模板。我认为它是一种极端主义的编程形式。:-) :-)
现在我的咆哮继续进行::-)
我认为当你把很多想法发挥到极致时,结果就会变得荒谬。有时(即这个话题)问题是我们把“错误”的想法发挥到了极致。
从视图中删除所有逻辑是“荒谬的”和错误的想法。
退后一会儿。
我们需要问自己的问题是为什么要删除逻辑?这个概念,显然是关注点分离。尽可能将视图处理与业务逻辑分开。为什么要这样做?它允许我们交换视图(针对不同的平台:移动、浏览器、桌面等),它允许我们更轻松地交换控制流、页面序列、验证更改、模型更改、安全访问等。从视图(尤其是 Web 视图)中删除,它使视图更具可读性,因此更易于维护。我明白并同意这一点。
然而,最重要的焦点应该是关注点的分离。不是 100% 无逻辑的视图。视图中的逻辑应该与如何渲染“模型”有关。就我而言,视图中的逻辑非常好。您可以拥有非业务逻辑的视图逻辑。
是的,在我们编写 JSP、PHP 或 ASP 页面时,代码逻辑和视图逻辑很少或根本没有分离的时候,这些 Web 应用程序的维护绝对是一场噩梦。相信我,我知道,我创造并维护了其中一些怪物。正是在那个维护阶段,我才真正理解(发自内心地)我和我的同事方式的错误。:-) :-)
因此,来自高层(行业专家)的法令变成了,您必须使用诸如前视图控制器之类的东西来构建您的 Web 应用程序(分派给处理程序或操作 [选择您的 Web 框架]),并且您的视图必须不包含任何代码. 视图将成为愚蠢的模板。
所以我大体上同意上述观点,不是因为法令条款的细节,而是法令背后的动机——即视图和业务逻辑之间的关注点分离的愿望。
在我参与的一个项目中,我们试图将无逻辑视图的想法推向可笑的极端。我们有一个自制的模板引擎,可以让我们在 html 中渲染模型对象。这是一个简单的基于令牌的系统。这很糟糕,原因很简单。有时在我们必须决定的视图中,我是否应该显示这个 HTML 的小片段......或者不......这个决定通常基于模型中的某些值。当您的视图中完全没有逻辑时,您如何做到这一点?好吧,你不能。我和我们的建筑师就这个问题发生了一些重大争论。编写我们观点的前端 HTML 人员在面对这个问题时完全束手无策,并且压力很大,因为他们无法实现原本简单的目标。所以我在我们的模板引擎中引入了一个简单的 IF 语句的概念。我无法向你描述随之而来的宽慰和平静。在我们的模板中使用一个简单的 IF 语句概念解决了问题!突然我们的模板引擎变得很好。
那么我们是如何陷入这种愚蠢的压力境地的呢?我们专注于错误的目标。我们遵守规则,你的观点不能有任何逻辑。那是错误的。我认为“经验法则”应该是,尽量减少您观点中的逻辑量。因为如果你不这样做,你可能会无意中让业务逻辑潜入视图——这违反了关注点分离。
我知道,当您声明“视图中必须没有逻辑”时,很容易知道您何时是“好”程序员。(如果这是你衡量善良的标准)。现在尝试使用上述规则实现一个中等复杂度的网络应用程序。它不是那么容易做到的。
对我来说,视图中的逻辑规则并不是那么明确,坦率地说,这就是我想要的。
当我在视图中看到大量逻辑时,我会检测到代码异味并尝试从视图中消除大部分逻辑——我尝试确保业务逻辑存在于其他地方——我尝试分离关注点。但是当我开始与那些说我们必须从视图中删除所有逻辑的人聊天时,嗯,对我来说,这只是有点狂热,因为我知道你最终可能会遇到我上面描述的情况。
我的咆哮结束了。:-)
干杯,
大卫
我为无逻辑模板提出的最佳论点是,您可以在客户端和服务器上使用完全相同的模板。但是,您实际上并不需要无逻辑的,只需要一种具有自己“语言”的语言。我同意那些抱怨胡须毫无意义的限制的人。谢谢,但我是个大男孩,没有你的帮助我可以保持我的模板干净。
另一种选择是找到一种模板语法,该语法使用客户端和服务器都支持的语言,即服务器上的 javascript,或者通过使用 node.js,或者您可以通过诸如 therubyracer 之类的东西使用 js 解释器和 json。
然后你可以使用 haml.js 之类的东西,它比目前提供的任何示例都干净得多,而且效果很好。
一句话:无逻辑意味着模板引擎本身不那么复杂,因此占用空间更小,并且出现意外行为的方式也更少。
即使这个问题已经过时并且得到了回答,我还是想加我的 2 美分(这听起来像是在咆哮,但事实并非如此,这是关于限制以及何时变得不可接受)。
模板的目标是渲染出一些东西,而不是执行业务逻辑。现在,在无法在模板中执行您需要执行的操作和在其中包含“业务逻辑”之间存在一条细线。尽管我对 Mustache 非常积极并尝试使用它,但在非常简单的情况下我最终无法做我需要的事情。
数据的“按摩”(使用已接受答案中的单词)可能成为一个真正的问题 - 甚至不支持简单的路径(Handlebars.js 解决的问题)。如果我有视图数据并且每次我想渲染某些东西时都需要调整它,因为我的模板引擎太有限了,那么这最终没有帮助。它击败了 mustache 声称的平台独立性的一部分。我必须到处复制按摩逻辑。
也就是说,在经历了一些挫折并尝试了其他模板引擎之后,我们最终创建了自己的(......还有另一个......),它使用了受 .NET Razor 模板启发的语法。它在服务器上进行解析和编译,并生成一个简单的、自包含的 JS 函数(实际上是 RequireJS 模块),可以调用该函数来“执行”模板,并返回一个字符串作为结果。使用我们的引擎时,brad 给出的示例看起来像这样(我个人认为与 Mustache 和 Underscore 相比,它的可读性要好得多):
@name:
<ul>
@for (items) {
<li>@.</li>
}
</ul>
当使用 Mustache 调用 partials 时,另一个无逻辑的限制打击了我们。虽然 Mustache 支持部分数据,但无法自定义要首先传入的数据。因此,我无法创建模块化模板并重用小块,我最终会使用其中重复代码的模板。
我们通过实现一种受 XPath 启发的查询语言(我们称之为 JPath)解决了这个问题。基本上,我们使用点而不是使用 / 遍历子项,不仅支持字符串、数字和布尔文字,还支持对象和数组(就像 JSON 一样)。该语言没有副作用(这是模板所必需的),但允许通过创建新的文字对象根据需要“按摩”数据。
假设我们想要渲染一个“数据网格”表,其中包含可自定义的标题和指向行上操作的链接,然后使用 jQuery 动态添加行。因此,如果我不想复制代码,这些行需要是部分的。如果某些附加信息(例如应呈现哪些列)是视图模型的一部分,并且每行上的这些操作都相同,那么问题就从这里开始了。这是使用我们的模板和查询引擎的一些实际工作代码:
表格模板:
<table>
<thead>
<tr>
@for (columns) {
<th>@title</th>
}
@if (actions) {
<th>Actions</th>
}
</tr>
</thead>
<tbody>
@for (rows) {
@partial Row({ row: ., actions: $.actions, columns: $.columns })
}
</tbody>
</table>
行模板:
<tr id="@(row.id)">
@for (var $col in columns) {
<td>@row.*[name()=$col.property]</td>
}
@if (actions) {
<td>
@for (actions) {
<button class="btn @(id)" value="@(id)">@(name)...</button>
}
</td>
}
</tr>
从 JS 代码调用:
var html = table({
columns: [
{ title: "Username", property: "username" },
{ title: "E-Mail", property: "email" }
],
actions: [
{ id: "delete", name: "Delete" }
],
rows: GetAjaxRows()
})
它没有任何业务逻辑,但它是可重用和可配置的,而且它也没有副作用。
使用无逻辑模板的主要优点是:
这里有 3 种渲染列表的方法,包括字符数。除了第一个和最短的一个之外,所有的都是无逻辑的模板语言。
CoffeeScript(带有Reactive Coffee builder DSL)- 37 个字符
"#{name}"
ul items.map (i) ->
li i
淘汰赛 - 100 个字符
<span data-bind="value: name"/>
<ul data-bind="foreach: items">
<li data-bind="value: i"/>
</ul>
车把/小胡子 - 66 个字符
{{name}}:
<ul>
{{#items}}
<li>{{.}}</li>
{{/items}}
</ul>
下划线 - 87 个字符
<%- name %>:
<ul>
<% _.each(items, function(i){ %>
<li><%- i %></li>
<% }); %>
</ul>
我想,无逻辑模板的承诺是,拥有更广泛技能的人将能够管理无逻辑模板,而不会自找麻烦。但是,您在上面的示例中看到的是,当您将最小逻辑语言添加到基于字符串的标记中时,结果会更复杂,而不是更少。此外,您看起来像是在使用老式 PHP。
显然,我不反对将“业务逻辑”(广泛计算)排除在模板之外。但我认为通过为他们提供一种用于显示逻辑的伪语言而不是一流的语言,是要付出代价的。不仅要输入更多内容,而且还需要阅读上下文切换的令人发指的组合。
总之,我看不到无逻辑模板的逻辑,所以我想说它们的优势对我来说是零,但我尊重社区中的许多人对它的不同看法:)
我同意布拉德的观点:这种underscore
风格更容易理解。但我必须承认语法糖可能并不适合所有人。如果_.each
有点混乱,您可以使用传统的for
循环。
<% for(var i = 0; i < items.length; i++) { %>
<%= items[i] %>
<% } %>
如果您可以回退到标准结构,例如for
or ,那总是很好的if
。只需使用<% if() %>
或<% for() %>
同时Mustache
使用一些新词if-then-else
(如果您没有阅读文档,则会感到困惑):
{{#x}}
foo
{{/x}}
{{^x}}
bar
{{/x}}
当您可以轻松实现嵌套模板(underscore
样式)时,模板引擎非常棒:
<script id="items-tmpl" type="text/template">
<ul>
<% for(var i = 0; i < obj.items.length; i++) { %>
<%= innerTmpl(obj.items[i]) %>
<% } %>
</ul>
</script>
<script id="item-tmpl" type="text/template">
<li>
<%= name %>
</li>
</script>
var tmplFn = function(outerTmpl, innerTmpl) {
return function(obj) {
return outerTmpl({obj: obj, innerTmpl: innerTmpl});
};
};
var tmpl = tmplFn($('#items-tmpl').html(), $('#item-tmpl').html());
var context = { items: [{name:'A',{name:'B'}}] };
tmpl(context);
基本上,您将内部 tmpl 作为上下文的属性传递。并相应地调用它。甜的 :)
顺便说一句,如果您唯一感兴趣的东西是模板引擎,请使用独立的模板实现。缩小后只有 900 个字符(4 长行):