Jinja2 模板引擎(上):变量渲染、循环与条件判断
📂 所属阶段:第一阶段 — 破冰启航(基础篇)
🔗 相关章节:路由(Routing)艺术 · Jinja2 模板引擎(下)
1. 先搞懂:Jinja2 是啥?为啥要用?
Jinja2 是 Flask 生态下默认集成、开箱即用的现代模板引擎,专门负责把后端数据「揉」进 HTML 骨架里。
和纯 Python 字符串拼接写 HTML 相比,它用了清晰的两种分隔符区分「展示内容」和「执行逻辑」,可读性、可维护性直接拉满:
{{ 变量/表达式 }} → 输出转义后的变量值
{% 逻辑/控制结构 %} → 执行条件、循环、宏这些
# Flask 会自动在根目录下的 `templates/` 文件夹找同名模板
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
# 用关键字参数传数据给模板,也可以用字典解包:**context
return render_template(
"index.html",
page_title="我的 Flask 博客首页",
current_user={"name": "Alice", "is_logged_in": True}
)
2. 第一步:基础变量渲染
把后端数据「贴」到页面
2.1 简单变量/属性/字典索引
Jinja2 访问数据的方式和 Python 原生一致,两种属性、字典索引通用
<!-- templates/index.html -->
<header>
<h1>{{ page_title }}</h1>
</header>
<main>
<!-- 点号访问对象属性/字典键 -->
<p>Hi, {{ current_user.name }} 👋</p>
<!-- 方括号字典索引(适合带特殊字符的键) -->
<p>邮箱前缀:{{ current_user["user_id"] }}</p>
</main>
2.2 实用过滤器(Filters):给变量「一键美化/处理」
过滤器就是 Jinja2 内置的一套「变量处理管道」,可以链式叠加(像 Linux 管道符 | 一样)
<!-- 假设后端传了:
name=" alice ", price=29.99, bio=None, tags=["Flask", "Jinja2"] -->
<section>
<h2>变量处理小工具</h2>
<ul>
<li>全大写:{{ name|upper }}</li>
<li>首字母大写:{{ name|trim|capitalize }}</li> <!-- 先去首尾空格 → 再首字母大写</li>
<li>默认值:{{ bio|default("这位博主还没写简介") }}</li>
<li>数字四舍五入:{{ price|round(1) }}</li>
<li>安全 HTML(⚠️ 仅用于**绝对信任的内容,比如自己写的静态文案):{{ "<b>这是加粗的安全内容</b>"|safe }}</li>
<li>去除 HTML 标签:{{ "<i>我是有HTML标签内容</i>"|striptags }}</li>
<li>列表连接成字符串:{{ tags|join(" · ") }}</li>
<li>变量长度:{{ tags|length }}</li>
</ul>
</section>
2.3 高频内置过滤器速查表
3. 逻辑控制第一步:条件判断 {% if %}
根据后端数据的不同,展示不同的 HTML 内容
3.1 完整分支 if / elif / else
可以嵌套使用,但**尽量控制在 2-3 层内,可读性优先
<!-- 假设后端传了 current_user 包含 is_logged_in、role -->
<nav>
{% if current_user.is_logged_in %}
<span>👤 {{ current_user.name }}</span>
{% if current_user.role == "admin" %}
<a href="/admin">管理后台</a>
{% elif current_user.role == "editor" %}
<a href="/editor">编辑后台</a>
{% else %}
<a href="/profile">个人中心</a>
{% endif %}
<a href="/logout">退出登录</a>
{% else %}
<a href="/login">登录</a>
<a href="/register">注册</a>
{% endif %}
</nav>
3.2 常用判断运算符
Jinja2 的运算符和 Python 几乎一样,还加了几个专门的「模板测试」
<!-- 假设后端传了 articles=[...] -->
<section>
<h2>运算符测试</h2>
<ul>
<!-- 常用比较/逻辑运算符 -->
{% if current_user.age is defined and current_user.age >= 18 %}
<li>✅ 成年用户,可访问全部内容</li>
{% endif %}
{% if articles[0].views > 1000 and "Python" in articles[0].tags %}
<li>🔥 Python 热门文章推荐</li>
{% endif %}
{% if "banned" not in current_user.roles %}
<li>💬 可以发言评论</li>
{% endif %}
<!-- 专门的「模板测试」(用 is/ is not) -->
{% if current_user.bio is none %}
<li>ℹ️ 个人简介待完善</li>
{% endif %}
{% if articles is iterable %}
<li>📝 文章列表已准备好渲染</li>
{% endif %}
</ul>
</section>
4. 逻辑控制核心:循环 {% for %}
渲染列表、字典这类可迭代的后端数据
4.1 基础循环:遍历列表
<!-- 假设后端传了 articles 列表 -->
<section>
<h2>最新文章</h2>
<ul class="article-list">
{% for article in articles %}
<li>
<a href="{{ url_for('show_article', id=article.id) }}">
{{ article.title }}
</a>
<span class="meta">{{ article.published_at }} · {{ article.views }} 阅读</span>
</li>
{% endfor %}
</ul>
</section>
4.2 循环专属变量 loop.xxx
Jinja2 自动给每个循环生成了方便的内置变量,帮你处理序号、首尾判断这些
<section>
<h2>带专属效果的文章列表</h2>
<div class="article-cards">
{% for article in articles %}
<div class="card {% if loop.first %}highlight{% endif %}">
<!-- 序号类 -->
<span class="index">#{{ loop.index }}</span>
<!-- 反向序号(最后一篇是 #1) -->
<span class="rev-index">(倒数第{{ loop.revindex }})</span>
<!-- 总数 -->
<span class="total">共{{ loop.length }}篇</span>
<h3>{{ article.title }}</h3>
<p>{{ article.summary }}</p>
</div>
{% endfor %}
</div>
</section>
4.3 遍历字典:`items() / keys() / values()
和 Python 一样,三种遍历方式任选
<section>
<h2>用户信息表</h2>
<table>
<thead>
<tr>
<th>属性名</th>
<th>属性值</th>
</tr>
</thead>
<tbody>
{% for key, value in current_user.items() %}
<tr>
<td>{{ key|replace("_", " ") }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
4.4 空循环兜底:{% else %}
注意这个 else 是跟在 for 后面的!只有循环一次都没执行(可迭代对象为空)时触发
<section>
<h2>草稿箱</h2>
<ul class="draft-list">
{% for draft in drafts %}
<li>{{ draft.title }}</li>
{% else %}
<p>🎉 暂无草稿!</p>
{% endfor %}
</ul>
</section>
5. 代码复用利器:宏 {% macro %}
像 Python 函数一样,把重复的 HTML 模板代码封装成「宏」,避免复制粘贴
5.1 定义宏
建议单独建一个 templates/macros.html 存全局用的宏
<!-- templates/macros.html -->
{# 宏定义:渲染单个文章卡片 #}
{% macro render_article_card(article, show_published=False) %}
<div class="card">
<h3>{{ article.title }}</h3>
<p>{{ article.summary|truncate(100) }}</p> {# truncate 也是常用过滤器:截断文本到 100 字符,默认加省略号 #}
{% if show_published %}
<span class="published-at">{{ article.published_at }}</span>
{% endif %}
<a href="{{ url_for('show_article', id=article.id) }}" class="btn">阅读更多</a>
</div>
{% endmacro %}
{# 宏定义:渲染单个表单字段(和 Flask-WTF 搭配用的通用字段 #}
{% macro render_wtf_field(field) %}
<div class="form-group">
{{ field.label(class="form-label") }}
{{ field(class="form-control") }}
{% if field.errors %}
<small class="text-danger">{{ field.errors[0] }}</small>
{% endif %}
</div>
{% endmacro %}
5.2 导入宏
可以导入整个宏文件,也可以只导入需要的宏(推荐只导需要的,更轻量)
<!-- templates/articles.html -->
{# 只导入需要的宏 #}
{% from "macros.html" import render_article_card %}
<section>
<h2>精选文章</h2>
<div class="grid">
{% for article in featured_articles %}
{{ render_article_card(article, show_published=True) }}
{% endfor %}
</div>
</section>
6. 安全第一:自动转义与块级处理
6.1 Flask 自动转义:默认防 XSS 攻击
Flask 默认会自动把输出的 HTML 特殊字符(比如 <、>、&、")转义成实体,防止恶意用户输入的 <script> 等代码被浏览器执行
<!-- 假设后端传了:malicious_content="<script>alert('恶意弹窗!')</script>" -->
<p>{{ malicious_content }}</p>
<!-- 浏览器实际显示的是:<script>alert('恶意弹窗!')</script> → 完全不会弹窗!
如果内容是自己写的静态文案、或者经过严格审核的内容,可以用 safe 过滤器取消转义:
<p>{{ "<b>Flask 真好用!</b>"|safe }}</p>
<!-- 浏览器会显示加粗的文字 -->
6.2 块级免解析:{% raw %}
整块内容不想被 Jinja2 解析,直接原封不动输出
<section>
<h2>Jinja2 语法示例</h2>
<pre>
{% raw %}
这里的 {{ 变量 }} 和 {% if %} 控制结构不会被解析
原封不动显示给用户看
{% endraw %}
</pre>
</section>
7. 快速小结
Jinja2 核心语法(基础篇):
{{ variable }} 输出转义后的变量
{{ name|upper }} 变量过滤器
{% if cond %} 条件判断
{% for item in list %} 循环
{% for ... %} {% else %} {% endfor %} 循环兜底(空循环时执行
{% macro name() %} 宏定义(代码复用
{% from "file.html" import ... %} 导入宏
{% raw %} {% endraw %} 块级免解析
{{ url_for('func') }} Flask 内置反向生成 URL
{{ session['key'] }} 直接访问 session
{{ request.args.get('q') }} 直接访问请求参数
💡 核心最佳实践:模板里只做展示相关的逻辑(比如简单的判断、循环、变量过滤),复杂的业务逻辑、数据处理全部放到后端视图函数、或者自定义 Jinja2 扩展里处理!!!这样前后端(模板和视图)的职责就划分清晰,代码更易维护~
🔗 扩展阅读