模板片段是服务器端渲染(SSR)模板库中一个相对少见的功能,它允许你渲染模板中的片段或部分内容,而不是整个模板。这个功能在超媒体驱动应用中非常有用,因为它允许你在内部为部分更新分解特定视图,而无需将模板片段提取到单独的文件中进行渲染,从而避免创建大量单独的模板文件。
通过将所有HTML保存在单个文件中,也更容易理解功能的工作原理。这遵循了行为局部性设计原则。
让我们看看模板片段如何在一个名为chill模板(一种用于Java的冷门模板语言)中帮助我们构建超媒体驱动应用。
这是一个简单的chill模板/contacts/detail.html
,用于显示联系人:
<html>
<body>
<div hx-target="this">
#if contact.archived
<button hx-patch="/contacts/${contact.id}/unarchive">取消归档</button>
#else
<button hx-delete="/contacts/${contact.id}">归档</button>
#end
</div>
<h3>联系人</h3>
<p>${contact.email}</p>
</body>
</html>
在模板中,我们有一个归档功能,根据联系人的归档状态,我们显示"归档"或"取消归档"按钮,两者都由htmx驱动,并向不同的端点发出HTTP请求。
当我们点击显示的任一按钮时,我们希望用更新后的按钮替换包含该按钮的div
中的内容。(注意div上的hx-target="this"
,因此我们将该div的innerHTML作为替换目标。)这将有效地在"归档"和"取消归档"之间切换。
现在,不幸的是,如果我们只想渲染按钮而不是模板的其余部分,通常需要将按钮拆分到它们自己的模板文件中,并在此模板中包含它,如下所示:
<html>
<body>
<div hx-target="this">
#include archive-ui.html
</div>
<h3>联系人</h3>
<p>${contact.email}</p>
</body>
</html>
#if contact.archived
<button hx-patch="/contacts/${contact.id}/unarchive">取消归档</button>
#else
<button hx-delete="/contacts/${contact.id}">归档</button>
#end
现在我们有两个模板。我们现在可以单独渲染archive-ui.html
模板,但这种拆分降低了归档功能的可见性:当只看detail.html
模板时,不太明显发生了什么。
当这种分解走向极端时,会导致相当多的小模板片段,总体上变得难以管理和理解。
为了解决这个问题,chill模板有一个#fragment
指令。这个指令允许你在模板中指定一个内容块,并仅渲染该部分内容:
<html>
<body>
<div hx-target="this">
#fragment archive-ui
#if contact.archived
<button hx-patch="/contacts/${contact.id}/unarchive">取消归档</button>
#else
<button hx-delete="/contacts/${contact.id}">归档</button>
#end
#end
</div>
<h3>联系人</h3>
<p>${contact.email}</p>
</body>
</html>
在模板中定义了这个片段后,我们现在可以渲染整个模板:
Contact c = getContact();
ChillTemplates.render("/contacts/detail.html", "contact", c);
或者我们可以只渲染模板的archive-ui
片段
Contact c = getContact();
ChillTemplates.render("/contacts/detail.html#archive-ui", "contact", c);
当我们要渲染联系人的整个详情页面时,会使用第一个选项。
当处理归档/取消归档操作并希望仅重新渲染按钮时,我们会使用第二个选项。
请注意,通过使用片段,我们能够将UI保持在一个文件中,并确切地了解该功能的工作原理,而无需在不同的模板文件之间跳转。这为该功能提供了更清晰、更明显的实现方式。
片段(以及直接在控制器中渲染它们的能力)似乎是模板库中相对罕见的功能,为使用htmx和其他超媒体导向库时改善开发者体验提供了绝佳机会。
以下是片段概念的一些已知实现:
$Latte_Engine->render('path/to/template.latte', [ 'foo' => 'bar' ], 'content');
$template->renderBlock('block_name', ['the' => 'variables', 'go' => 'here']);
如果你知道其他实现,请告诉我,以便我将它们添加到此列表中。