Skip to content

8.4 移除模板片段

在 Thymeleaf 模板开发中,为了让静态原型更逼真(比如显示多行模拟数据),但又要保证动态渲染时只保留真实数据,我们需要一种在模板处理时移除模拟片段的方式——这就是 th:remove 属性的核心作用。

回到上文我们提到的产品列表页:

html
<table>
    <tr>
        <th>NAME</th>
        <th>PRICE</th>
        <th>IN STOCK</th>
        <th>COMMENTS</th>
    </tr>
    <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
        <td>
            <span th:text="${#lists.size(prod.comments)}">2</span> comment/s
            <a href="comments.html"
               th:href="@{/product/comments(prodId=${prod.id})}"
               th:unless="${#lists.isEmpty(prod.comments)}">view</a>
        </td>
    </tr>
</table>

这段代码作为模板没有问题,但作为静态页面(当被浏览器直接打开而未经Thymeleaf处理时)无法成为理想的展示原型。

原因何在?因为尽管浏览器能正常显示这个表格,但它只有一行数据,并且这一行使用的是模拟数据。作为原型展示,这样的视觉效果显然不够真实……我们需要展示多个产品,需要更多的数据行。

那就让我们加一些数据:

html
<table>
    <tr>
        <th>NAME</th>
        <th>PRICE</th>
        <th>IN STOCK</th>
        <th>COMMENTS</th>
    </tr>
    <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
        <td>
            <span th:text="${#lists.size(prod.comments)}">2</span> comment/s
            <a href="comments.html"
               th:href="@{/product/comments(prodId=${prod.id})}"
               th:unless="${#lists.isEmpty(prod.comments)}">view</a>
        </td>
    </tr>
    <tr class="odd">
        <td>Blue Lettuce</td>
        <td>9.55</td>
        <td>no</td>
        <td>
            <span>0</span> comment/s
        </td>
    </tr>
    <tr>
        <td>Mild Cinnamon</td>
        <td>1.99</td>
        <td>yes</td>
        <td>
            <span>3</span> comment/s
            <a href="comments.html">view</a>
        </td>
    </tr>
</table>

现在我们有了三行数据,作为原型来说确实好多了。但是……当我们用 Thymeleaf 处理它时,会发生什么呢?

html
<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
    <th>COMMENTS</th>
  </tr>
  <tr>
    <td>Fresh Sweet Basil</td>
    <td>4.99</td>
    <td>yes</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr class="odd">
    <td>Italian Tomato</td>
    <td>1.25</td>
    <td>no</td>
    <td>
      <span>2</span> comment/s
      <a href="/gtvg/product/comments?prodId=2">view</a>
    </td>
  </tr>
  <tr>
    <td>Yellow Bell Pepper</td>
    <td>2.50</td>
    <td>yes</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr class="odd">
    <td>Old Cheddar</td>
    <td>18.75</td>
    <td>yes</td>
    <td>
      <span>1</span> comment/s
      <a href="/gtvg/product/comments?prodId=4">view</a>
    </td>
  </tr>
  <tr class="odd">
    <td>Blue Lettuce</td>
    <td>9.55</td>
    <td>no</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr>
    <td>Mild Cinnamon</td>
    <td>1.99</td>
    <td>yes</td>
    <td>
      <span>3</span> comment/s
      <a href="comments.html">view</a>
    </td>
  </tr>
</table>

后面两行都是模拟数据!没错,它们确实是模拟的。迭代只应用于第一行,所以 Thymeleaf 没有理由去删除另外两行。

我们需要找到一种方法,在模板处理过程中移除这两行:在第二个和第三个 <tr> 标签上使用 th:remove 属性:

html
<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
    <th>COMMENTS</th>
  </tr>
  <!-- 真实数据行:th:each 遍历生成 -->
  <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
    <td>
      <span th:text="${#lists.size(prod.comments)}">2</span> comment/s
      <a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view</a>
    </td>
  </tr>
  <!-- 模拟行:处理时完全移除 -->
  <tr class="odd" th:remove="all">
    <td>Blue Lettuce</td>
    <td>9.55</td>
    <td>no</td>
    <td><span>0</span> comment/s</td>
  </tr>
  <tr th:remove="all">
    <td>Mild Cinnamon</td>
    <td>1.99</td>
    <td>yes</td>
    <td><span>3</span> comment/s<a href="comments.html">view</a></td>
  </tr>
</table>

处理后仅保留真实数据行,模拟行被彻底移除,页面显示正常。

html
<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
    <th>COMMENTS</th>
  </tr>
  <tr>
    <td>Fresh Sweet Basil</td>
    <td>4.99</td>
    <td>yes</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr class="odd">
    <td>Italian Tomato</td>
    <td>1.25</td>
    <td>no</td>
    <td>
      <span>2</span> comment/s
      <a href="/gtvg/product/comments?prodId=2">view</a>
    </td>
  </tr>
  <tr>
    <td>Yellow Bell Pepper</td>
    <td>2.50</td>
    <td>yes</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr class="odd">
    <td>Old Cheddar</td>
    <td>18.75</td>
    <td>yes</td>
    <td>
      <span>1</span> comment/s
      <a href="/gtvg/product/comments?prodId=4">view</a>
    </td>
  </tr>
</table>

上文中,属性all的作用是什么?th:remove 支持5种取值,对应不同的移除逻辑:

取值行为描述
all移除包含标签 及其所有子元素(最常用,如上述模拟行的移除)
body保留包含标签,但移除其所有子元素(标签本身保留,内容清空)
tag移除包含标签,但保留其所有子元素(只删标签,内容保留)
all-but-first移除包含标签的所有子元素,仅保留第一个子元素(批量移除模拟行更高效)
none不执行任何移除操作(用于动态条件判断)

all-but-first 这个值有什么用处呢?它能让我们在制作原型时,无需给每个模拟行添加th:remove="all"

html
<table>
  <thead>
    <tr>
      <th>NAME</th>
      <th>PRICE</th>
      <th>IN STOCK</th>
      <th>COMMENTS</th>
    </tr>
  </thead>
  <!-- 仅保留第一个子元素(真实数据行),移除其他模拟行 -->
  <tbody th:remove="all-but-first">
    <!-- 第一个子元素:真实数据行(保留) -->
    <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
        <td>
            <span th:text="${#lists.size(prod.comments)}">2</span> comment/s
            <a href="comments.html"
               th:href="@{/product/comments(prodId=${prod.id})}"
               th:unless="${#lists.isEmpty(prod.comments)}">view</a>
        </td>
    </tr>
    <!-- 后续子元素:模拟行(自动移除) -->
    <tr class="odd">
        <td>Blue Lettuce</td>
        <td>9.55</td>
        <td>no</td>
        <td>
            <span>0</span> comment/s
        </td>
    </tr>
    <tr>
        <td>Mild Cinnamon</td>
        <td>1.99</td>
        <td>yes</td>
        <td>
            <span>3</span> comment/s
            <a href="comments.html">view</a>
        </td>
    </tr>
  </tbody>
</table>

th:remove 可接收任意 Thymeleaf 标准表达式,只要返回上述5种取值之一。

实现“条件满足才移除”:

html
<!-- 条件为 true 时移除标签(保留内容),false 时不操作 -->
<a href="/something" th:remove="${condition}? tag : none">Link text not to be removed</a>

th:remove中,null 等价于 none,所以上面的代码也可以写成这样:

html
<!-- 简化写法:null 等价于 none,条件为 false 时返回 null,不移除 -->
<a href="/something" th:remove="${condition}? tag">Link text not to be removed</a>

示例逻辑:${condition}true → 移除 <a> 标签(保留链接文本);为 false → 不做任何移除。

典型场景总结

场景推荐取值效果
移除整行/整段模拟数据all彻底删除标签+内容
清空容器内容(保留容器标签)body只删内容,保留标签
移除外层标签(保留内部内容)tag只删标签,保留内容
批量保留首行、移除后续模拟行all-but-first高效批量处理模拟数据
动态控制是否移除表达式(如 ${cond}? tag : none条件化移除

总结

  1. th:remove 解决了“静态原型需要模拟数据,动态渲染需要移除模拟数据”的核心矛盾;
  2. 核心取值:all(全删)、body(删内容)、tag(删标签)、all-but-first(仅保留首个子元素)、none(不删);
  3. 支持动态表达式,可根据条件控制移除行为,兼顾灵活性和静态原型的友好性。