3、模板语法基础:变量与插值、条件判断、循环遍历、过滤器与管道

各位好,欢迎来到第三章。说实话,模板语法是每个模板工程师的「吃饭家伙」。你想想看,如果连变量怎么插、条件怎么写都搞不清楚,后面那些复杂的组件复用、布局嵌套根本玩不转。

我个人习惯把模板语法分成三块来记:数据怎么进来(变量与插值)、逻辑怎么控制(条件与循环)、数据怎么加工(过滤器与管道)。咱们一个一个来。

3.1 变量与插值:让模板「活」起来

模板最核心的能力,就是把后端传过来的数据塞到 HTML 里。说白了,就是让页面不再是死板的静态文件。

在大多数模板引擎里(比如 Jinja2、Nunjucks、Thymeleaf),变量插值都用双花括号:

<!-- 基础变量插值 -->
<p>欢迎您,{{ username }}!</p>

<!-- 对象属性访问 -->
<p>您的订单编号:{{ order.id }},金额:{{ order.total }}</p>

<!-- 列表索引 -->
<p>第一个商品:{{ products[0].name }}</p>

嗯,这里要注意:花括号里不能写复杂的 JavaScript 表达式。只能放变量名、点号访问、中括号索引。为什么?因为模板引擎的设计原则就是「逻辑与视图分离」,你非要在模板里写一堆运算逻辑,那还不如直接用 JS 渲染。

我的小技巧: 变量名尽量用下划线命名法(如 user_name),别用驼峰。因为很多后端语言(Python、PHP)习惯用下划线,前后端统一能少踩很多坑。

3.2 条件判断:让模板「会思考」

条件判断是模板里最常用的逻辑控制。我在项目中遇到过最典型的场景:用户登录状态不同,页面展示完全不一样。

以 Jinja2 为例,条件判断长这样:

{% if user.is_login %}
  <p>欢迎回来,{{ user.name }}!</p>
{% elif user.is_vip %}
  <p>尊贵的 VIP 用户,您有专属折扣!</p>
{% else %}
  <p>请先登录或注册。</p>
{% endif %}

注意看,模板引擎用 {% %} 包裹控制语句,用 {{ }} 输出变量。这个区别一定要记牢——控制语句不输出内容,变量插值才输出。

我曾经踩过的坑: 在条件判断里直接写 {% if user %},以为能判断「用户对象是否存在」。结果 user 是个空对象,条件为真,页面直接崩了。后来我养成了习惯:{% if user is defined and user %},先判断变量是否存在,再判断值是否为真。

3.3 循环遍历:批量生成内容

循环遍历说白了就是「重复劳动交给模板」。你想想看,一个商品列表有 50 个商品,难道手写 50 个 <li>?当然不。

标准写法:

<ul>
{% for product in products %}
  <li>
    <span>{{ product.name }}</span>
    <span>¥{{ product.price }}</span>
  </li>
{% endfor %}
</ul>

很多模板引擎还提供了循环辅助变量,比如:

变量名 说明 示例
loop.index 当前循环次数(从1开始) 第 1 项、第 2 项...
loop.first 是否是第一次循环 给第一个元素加特殊样式
loop.last 是否是最后一次循环 最后一个元素不加分隔线
loop.length 循环总次数 显示「共 N 条」

我个人习惯在循环里加个 loop.first 判断,给第一个元素加个「新品」标签,用户体验会好很多。

3.4 过滤器与管道:数据加工的「流水线」

过滤器,说白了就是「把数据扔进去,加工一下再吐出来」。比如用户输入了一串英文名,你想让它首字母大写;或者后端返回了一个时间戳,你想格式化成「2024-01-15」这种样子。

在 Jinja2 和 Nunjucks 里,过滤器用管道符 | 连接:

<!-- 字符串大写 -->
<p>{{ "hello world" | upper }}</p>
<!-- 输出:HELLO WORLD -->

<!-- 默认值 -->
<p>{{ user.nickname | default("匿名用户") }}</p>

<!-- 日期格式化 -->
<p>{{ article.publish_time | date("Y-m-d") }}</p>

<!-- 链式调用:先转大写,再截取前5个字符 -->
<p>{{ "hello world" | upper | truncate(5) }}</p>
<!-- 输出:HELLO... -->

为什么叫「管道」?你可以想象成一根水管:数据从一头流进去,经过一个个过滤器(就像滤网),最后流出来的是加工好的数据。这个设计思想在 Linux 命令行里也有,模板引擎借鉴了它。

核心原则: 过滤器只做数据转换,不做业务逻辑。比如「如果用户是VIP,价格打8折」这种逻辑应该在后端做,模板里只负责展示打折后的价格。

3.5 实战中的组合使用

来,咱们看一个真实场景。我在做电商后台时,需要渲染一个订单列表:

<table>
  <thead>
    <tr>
      <th>#</th>
      <th>商品名称</th>
      <th>单价</th>
      <th>数量</th>
      <th>小计</th>
      <th>状态</th>
    </tr>
  </thead>
  <tbody>
  {% for item in order.items %}
    <tr class="{{ 'bg-light' if loop.first }}">
      <td>{{ loop.index }}</td>
      <td>{{ item.name | default('未知商品') }}</td>
      <td>¥{{ item.price | round(2) }}</td>
      <td>{{ item.quantity }}</td>
      <td>¥{{ item.price * item.quantity | round(2) }}</td>
      <td>
        {% if item.status == 'paid' %}
          <span class="tag tag-green">已支付</span>
        {% elif item.status == 'shipped' %}
          <span class="tag tag-blue">已发货</span>
        {% else %}
          <span class="tag tag-gray">待处理</span>
        {% endif %}
      </td>
    </tr>
  {% endfor %}
  </tbody>
</table>

你看,这里把变量插值、条件判断、循环遍历、过滤器全用上了。而且我特意在 loop.first 时加了背景色,让第一行更醒目——这就是模板工程师的「小心机」。

3.6 避坑指南

最后,分享几个我这些年踩过的坑:

  • 过滤器顺序很重要:{{ value | filter1 | filter2 }} 和 {{ value | filter2 | filter1 }} 结果可能完全不同。我建议先做「清洗型」过滤(如去空格、转小写),再做「展示型」过滤(如截断、格式化)。
  • 不要在循环里写复杂条件:比如 {% for item in items if item.price > 100 and item.stock > 0 %},这种写法可读性极差。我一般会在后端先过滤好数据,模板只负责展示。
  • 变量不存在时怎么办:很多模板引擎默认会报错。我习惯给所有变量加 default 过滤器,或者开启模板引擎的「静默模式」——宁可显示空白,也别让页面崩掉。

好了,这一章的内容就这些。模板语法看着简单,但真正用好需要大量实践。下一章咱们聊聊模板继承与布局,那才是真正提升效率的利器。