在 FreeMarker 模板引擎中,<#nested><#macro> 的一个强大特性,用于在宏定义中插入调用者提供的内容。它使得宏不仅可以接收参数,还可以接收嵌套的 HTML 或文本内容,从而实现布局模板组件封装条件包裹等高级功能。

本文将从 核心概念操作步骤常见错误注意事项使用技巧最佳实践性能优化 等多个维度,全面讲解 FreeMarker 中 <#nested> 的使用,帮助你快速掌握关键知识并实践。


一、核心概念

概念 描述
<#nested> 宏中用于插入调用者提供内容的占位符
<#macro> 定义宏时,可以包含 <#nested> 作为内容占位符
调用方式 使用 <@macroName>...</@macroName> 包裹内容进行调用
嵌套层级 支持多层嵌套,但建议控制层级以提高可读性
参数传递 可结合 <#nested> 使用宏参数,实现动态内容包裹

二、操作步骤(详细)

1. 定义包含 <#nested> 的宏

<#macro box>
  <div class="box">
    <#nested>
  </div>
</#macro>

2. 调用宏并传入内容

<@box>
  <p>This is the content inside the box.</p>
</@box>

输出结果:

<div class="box">
  <p>This is the content inside the box.</p>
</div>

2. 定义带标题和内容的宏

<#macro box title>
  <div class="box">
    <h3>${title}</h3>
    <div class="content">
      <#nested>
    </div>
  </div>
</#macro>

调用方式:

<@box title="User Info">
  <p>Name: Alice</p>
  <p>Age: 25</p>
</@box>

3. 嵌套多个 <#nested>(FreeMarker 2.3.29+)

<#macro layout header footer>
  <html>
    <head><title>Page</title></head>
    <body>
      <header>
        <#nested "header">
      </header>
      <main>
        <#nested>
      </main>
      <footer>
        <#nested "footer">
      </footer>
    </body>
  </html>
</#macro>

调用方式:

<@layout>
  <#nested header>
    <h1>Header Content</h1>
  </#nested>
  <p>Main content here.</p>
  <#nested footer>
    <p>Footer Content</p>
  </#nested>
</@layout>

4. <#nested><#local> 结合使用

<#macro greet name>
  <#local greeting = "Hi">
  <#nested>
  <p>${greeting}, ${name}!</p>
</#macro>

调用方式:

<@greet name="Alice">
  <p>Welcome to our site.</p>
</@greet>

三、常见错误与注意事项

1. <#nested> 未被包裹在 <#macro>

<#nested>  <#-- 错误:不在宏中 -->

解决方法:确保 <#nested> 只在宏定义中使用。


2. <#nested> 调用时未提供内容

<@box />  <#-- 无内容,<#nested> 不会渲染 -->

建议:使用 <#if> 判断是否有内容再渲染 <#nested>


3. <#nested> 嵌套标签未正确闭合

<@box title="Title">  <#-- 缺少闭合标签 -->

解决方法:必须使用 <@box title="Title">...</@box> 包裹内容。


4. <#nested> 参数名称不匹配

<#macro layout>
  <#nested "header">
</#macro>

<@layout>
  <#nested "footer">  <#-- 错误:没有定义 footer 区域 -->
</@layout>

解决方法:宏中定义的 <#nested> 参数必须与调用时一致。


四、使用技巧

1. 使用 <#nested> 实现页面布局(布局模板)

<#macro layout title>
  <html>
    <head><title>${title}</title></head>
    <body>
      <#nested>
    </body>
  </html>
</#macro>

调用方式:

<@layout title="Home">
  <h1>Welcome</h1>
  <p>This is the home page.</p>
</@layout>

2. 使用 <#nested> 实现组件包裹(如卡片、按钮)

<#macro card title>
  <div class="card">
    <h3>${title}</h3>
    <div class="content">
      <#nested>
    </div>
  </div>
</#macro>

调用方式:

<@card title="User Info">
  <p>Name: Alice</p>
  <p>Age: 25</p>
</@card>

3. 使用 <#nested> 封装条件逻辑

<#macro ifNotEmpty list>
  <#if list?has_content>
    <#nested>
  </#if>
</#macro>

调用方式:

<@ifNotEmpty list=users>
  <ul>
    <#list users as user>
      <li>${user.name}</li>
    </#list>
  </ul>
</@ifNotEmpty>

4. 使用 <#nested> 实现多区域布局

<#macro layout header footer>
  <html>
    <head><title>Page</title></head>
    <body>
      <header><#nested "header"></header>
      <main><#nested></main>
      <footer><#nested "footer"></footer>
    </body>
  </html>
</#macro>

调用方式:

<@layout>
  <#nested header><h1>Header</h1></#nested>
  <p>Main content</p>
  <#nested footer><p>Footer</p></#nested>
</@layout>

五、最佳实践

实践 说明
✅ 使用 <#nested> 实现布局模板 如页面结构、盒子结构等
✅ 使用 <#nested> 封装 UI 组件 如卡片、按钮、表格行等
✅ 使用 <#nested> 实现条件包裹 ifNotEmptyifLoggedIn
✅ 使用 <#local> 避免变量污染 保证宏内部变量不干扰外部作用域
✅ 宏名统一命名规范 layout.maincomponent.card
✅ 控制嵌套层级 避免嵌套过深影响可读性

六、性能优化

优化点 说明
✅ 避免 <#nested> 中频繁调用方法 方法调用会降低性能,建议预处理后传入
✅ 启用模板缓存 FreeMarker 支持模板缓存,避免重复解析
✅ 合理使用 <#nested> 参数 避免过多区域定义,影响渲染效率
✅ 减少 <#nested> 嵌套层级 多层嵌套会增加渲染复杂度

七、总结

概念 说明 推荐使用场景
<#nested> 宏中插入调用者内容 布局模板、UI 组件、逻辑封装
<#nested "name"> 多区域嵌套内容 多区域布局、条件分块
<#macro> 定义宏模板 模块化开发、内容复用
<#local> 宏内局部变量 避免变量污染
<#nested> + <#if> 条件包裹内容 ifNotEmptyifLoggedIn
嵌套宏 宏中调用其他宏 提高模板模块化程度

八、参考示例代码(Java + FTL)

Java 示例数据

Map<String, Object> model = new HashMap<>();

List<Map<String, Object>> users = new ArrayList<>();
Map<String, Object> user = new HashMap<>();
user.put("name", "Alice");
user.put("age", 25);
users.add(user);
model.put("users", users);

FTL 模板示例

<h2><#nested> 示例</h2>

<#macro card title>
  <div class="card">
    <h3>${title}</h3>
    <div class="content">
      <#nested>
    </div>
  </div>
</#macro>

<#macro layout title>
  <html>
    <head><title>${title}</title></head>
    <body>
      <#nested>
    </body>
  </html>
</#macro>

<#-- 使用 card 宏 -->
<@card title="User Info">
  <p>Name: Alice</p>
  <p>Age: 25</p>
</@card>

<#-- 使用 layout 宏 -->
<@layout title="User Page">
  <h2>User List</h2>
  <#list users as user>
    <@card title="User Info">
      <p>Name: ${user.name}</p>
      <p>Age: ${user.age}</p>
    </@card>
  </#list>
</@layout>

九、结语

掌握 FreeMarker 中 <#nested> 的使用,是构建高效、模块化模板系统的关键。通过 <#nested>,你可以实现布局模板组件封装条件包裹等高级功能,极大提升模板的可读性、可维护性和复用性。

最终建议

  • 所有需要包裹内容的宏都应使用 <#nested>
  • 使用 <#nested> 实现布局和组件封装;
  • 使用 <#local> 避免宏变量污染;
  • 控制 <#nested> 嵌套层级;
  • 启用模板缓存提升性能;
  • 宏名统一命名规范,避免冲突。