当前位置: 首页 > news >正文

Rust Web开发指南 第六章(动态网页模板技术-MiniJinja速成教程)

MiniJinja 是 Rust 生态中一款轻量、高效的模板引擎,语法贴近 Python 的 Jinja2,适合快速实现动态文本生成。本教程基于最新的 2.12.0 版本,将带你快速掌握其核心用法。

一、准备工作

安装依赖

在 Cargo.toml 中添加最新版本依赖:

[dependencies]
minijinja = "2.12.0"  # 最新稳定版本

二、核心概念

  1. Environment(环境):模板引擎的核心容器,管理模板、过滤器、全局配置等。
  2. 模板(Template):包含静态文本和动态占位符的字符串,定义输出格式。
  3. 上下文(Context):传递给模板的动态数据(键值对),模板通过占位符引用。
  4. 过滤器(Filter):对模板数据进行加工的函数,语法为 {{ 变量 | 过滤器 }}

三、快速入门:第一个模板

示例代码

use minijinja::{Environment, context};fn main() -> Result<(), minijinja::Error> {  // 2.x 版本推荐使用 Result 处理错误// 1. 创建环境let mut env = Environment::new();// 2. 向环境添加模板(名称为"greeting")env.add_template("greeting", "Hello, {{ name }}! You are {{ age }} years old.")?;  // 推荐使用 ? 替代 unwrap()// 3. 获取模板let tmpl = env.get_template("greeting")?;// 4. 渲染模板:传入上下文(name=Alice, age=30)let result = tmpl.render(context! {name => "Alice",age => 30})?;  // 2.x 版本中 context! 宏语法略有调整// 输出:Hello, Alice! You are 30 years old.println!("{}", result);Ok(())
}

结果:

Hello, Alice! You are 30 years old.

四、模板语法基础

1. 变量引用

用 {{ 变量名 }} 引用上下文数据,支持嵌套结构:

use minijinja::{Environment, context};fn main() -> Result<(), minijinja::Error> {// 创建环境let mut env = Environment::new();// 添加模板,使用正确的嵌套变量引用env.add_template("disp", "发现了! {{ user.name }} 住在 {{ user.address.city }} 。")?;// 获取模板let tmpl = env.get_template("disp")?;// 使用正确的 context! 宏语法:key => valuelet data_ctx = context! {user => context! {name => "赵刚",address => context! {city => "北京"}}};// 渲染模板let result = tmpl.render(data_ctx)?;// 输出结果println!("{}", result);Ok(())
}

2. 过滤器(Filters)

语法: 过滤内容 | 过滤器名称

内置过滤器示例:

{{ user.address.city | upper }},通过upper过滤器,实现了将 user.address.city 转换为大写。

// 导入MiniJinja库中的核心组件:
// Environment:用于管理模板环境(存储模板、过滤器等配置)
// context:用于快速创建模板上下文(传递给模板的动态数据)
use minijinja::{Environment, context};// 定义主函数,返回Result类型以优雅处理可能的错误
// 错误类型指定为minijinja::Error,涵盖模板处理中的各类错误(如语法错误、变量缺失等)
fn main() -> Result<(), minijinja::Error> {// 创建一个可变的MiniJinja环境实例// 用mut修饰是因为后续需要向环境中添加模板(修改环境状态)let mut env = Environment::new();// 向环境中添加一个名为"disp"的模板// 模板内容包含:// - {{ user.name }}:引用上下文user对象的name属性// - {{ user.address.city | upper }}:引用user.address.city,并通过upper过滤器转为大写// 使用?操作符处理可能的错误(如模板语法错误),若出错会向上传播env.add_template("disp", "发现了! {{ user.name }} 住在 {{ user.address.city | upper }} 。")?;// 从环境中获取名为"disp"的模板实例,用于后续渲染// ?操作符处理可能的错误(如模板不存在)let tmpl = env.get_template("disp")?;// 使用context!宏创建模板上下文(动态数据),采用嵌套结构:// - 顶层键为user,对应的值是一个嵌套的context!// - 嵌套的user上下文包含name(值为"ZhaoGang")和address(另一个嵌套context!)// - address上下文包含city(值为"beijing")// 键值对用=>分隔,符合MiniJinja 2.12.0的context!宏语法let data_ctx = context! {user => context! {name => "ZhaoGang",address => context! {city => "beijing"}}};// 调用模板的render方法,传入上下文数据进行渲染// 渲染过程会将模板中的变量占位符替换为上下文对应的值,并执行过滤器(如upper)// ?操作符处理渲染错误(如变量引用错误、过滤器不存在等)let result = tmpl.render(data_ctx)?;// 打印渲染后的结果// 预期输出:"发现了! ZhaoGang 住在 BEIJING 。"(beijing被upper过滤器转为大写)println!("{}", result);// 主函数正常执行完毕,返回Ok(())Ok(())
}
自定义过滤器:
(1)注册普通函数为过滤器

语法:env.add_filter("过滤器名字", 普通函数的名字);

// 导入MiniJinja库中的核心组件:
// Environment:用于管理模板环境(存储模板、过滤器等配置)
// context:用于快速创建模板上下文(传递给模板的动态数据)
use minijinja::{Environment, context};// 定义主函数,返回Result类型以优雅处理可能的错误
// 错误类型指定为minijinja::Error,涵盖模板处理中的各类错误(如语法错误、变量缺失等)
fn main() -> Result<(), minijinja::Error> {// 创建一个可变的MiniJinja环境实例// 用mut修饰是因为后续需要向环境中添加模板(修改环境状态)let mut env = Environment::new();// 将str::repeat(按照指定次数重复子字符串)直接注册成过滤器(之后模板就可以直接使用了!)env.add_filter("repeat", str::repeat);// 向环境中添加一个名为"disp"的模板// 模板内容包含:// - {{ user.name }}:引用上下文user对象的name属性// - {{ user.address.city | upper }}:引用user.address.city,并通过upper过滤器转为大写// 使用?操作符处理可能的错误(如模板语法错误),若出错会向上传播env.add_template("disp", "发现了! {{ user.name }} 住在 {{ user.address.city | repeat(3) }} 。")?;// 从环境中获取名为"disp"的模板实例,用于后续渲染// ?操作符处理可能的错误(如模板不存在)let tmpl = env.get_template("disp")?;// 使用context!宏创建模板上下文(动态数据),采用嵌套结构:// - 顶层键为user,对应的值是一个嵌套的context!// - 嵌套的user上下文包含name(值为"ZhaoGang")和address(另一个嵌套context!)// - address上下文包含city(值为"beijing")// 键值对用=>分隔,符合MiniJinja 2.12.0的context!宏语法let data_ctx = context! {user => context! {name => "赵刚",address => context! {city => "北京"}}};// 调用模板的render方法,传入上下文数据进行渲染// 渲染过程会将模板中的变量占位符替换为上下文对应的值,并执行过滤器(如upper)// ?操作符处理渲染错误(如变量引用错误、过滤器不存在等)let result = tmpl.render(data_ctx)?;// 打印渲染后的结果// 预期输出:"发现了! ZhaoGang 住在 BEIJING 。"(beijing被upper过滤器转为大写)println!("{}", result);// 主函数正常执行完毕,返回Ok(())Ok(())
}

结果:repeat过滤器起了作用!

发现了! 赵刚 住在 北京北京北京 。
(2)注册自定义函数为过滤器

先自定义函数,之后同上一样,将其注册为过滤器。

// 导入MiniJinja库中的核心组件:
// Environment:用于管理模板环境(存储模板、过滤器等配置)
// context:用于快速创建模板上下文(传递给模板的动态数据)
use minijinja::{Environment, context};// 定义主函数,返回Result类型以优雅处理可能的错误
// 错误类型指定为minijinja::Error,涵盖模板处理中的各类错误(如语法错误、变量缺失等)
fn main() -> Result<(), minijinja::Error> {// 创建一个可变的MiniJinja环境实例// 用mut修饰是因为后续需要向环境中添加模板(修改环境状态)let mut env = Environment::new();// 将自定义函数reverse直接注册成过滤器(之后模板就可以直接使用了!)env.add_filter("fanzhuan", reverse);// 向环境中添加一个名为"disp"的模板// 模板内容包含:// - {{ user.name }}:引用上下文user对象的name属性// - {{ user.address.city | upper }}:引用user.address.city,并通过fanzhuan过滤器进行翻转// 使用?操作符处理可能的错误(如模板语法错误),若出错会向上传播env.add_template("disp", "发现了! {{ user.name }} 住在 {{ user.address.city | fanzhuan }} 。")?;// 从环境中获取名为"disp"的模板实例,用于后续渲染// ?操作符处理可能的错误(如模板不存在)let tmpl = env.get_template("disp")?;// 使用context!宏创建模板上下文(动态数据),采用嵌套结构:// - 顶层键为user,对应的值是一个嵌套的context!// - 嵌套的user上下文包含name(值为"ZhaoGang")和address(另一个嵌套context!)// - address上下文包含city(值为"beijing")// 键值对用=>分隔,符合MiniJinja 2.12.0的context!宏语法let data_ctx = context! {user => context! {name => "赵刚",address => context! {city => "北京"}}};// 调用模板的render方法,传入上下文数据进行渲染// 渲染过程会将模板中的变量占位符替换为上下文对应的值,并执行过滤器(如upper)// ?操作符处理渲染错误(如变量引用错误、过滤器不存在等)let result = tmpl.render(data_ctx)?;// 打印渲染后的结果// 预期输出:"发现了! ZhaoGang 住在 BEIJING 。"(beijing被upper过滤器转为大写)println!("{}", result);// 主函数正常执行完毕,返回Ok(())Ok(())
}// 定义一个反转字符串的函数
fn reverse(s: &str) -> String {s.chars().rev().collect()
}

结果:

发现了! 赵刚 住在 京北 。

3. 条件判断

用 {% if ... %} 语法实现条件渲染:

// 导入MiniJinja库中的核心组件:
// Environment:用于管理模板环境(存储模板、过滤器等配置)
// context:用于快速创建模板上下文(传递给模板的动态数据)
use minijinja::{Environment, context};// 定义主函数,返回Result类型以优雅处理可能的错误
// 错误类型指定为minijinja::Error,涵盖模板处理中的各类错误(如语法错误、变量缺失等)
fn main() -> Result<(), minijinja::Error> {// 创建一个可变的MiniJinja环境实例// 用mut修饰是因为后续需要向环境中添加模板(修改环境状态)let mut env = Environment::new();// 创建一个模板,按照用户的地址是否在北京进行条件判断,如果地址是北京,则渲染成首都。let template_str = r#"下面显示{{user.name}}住址:{%if user.address.city=="北京"%}首都{%else%}{{user.address.city}}{%endif%}"#;// 向环境中添加一个名为"disp"的模板// 模板内容包含:// - {{ user.name }}:引用上下文user对象的name属性// - {{ user.address.city | upper }}:引用user.address.city,并通过upper过滤器转为大写// 使用?操作符处理可能的错误(如模板语法错误),若出错会向上传播env.add_template("disp", template_str)?;// 从环境中获取名为"disp"的模板实例,用于后续渲染// ?操作符处理可能的错误(如模板不存在)let tmpl = env.get_template("disp")?;// 使用context!宏创建模板上下文(动态数据),采用嵌套结构:// - 顶层键为user,对应的值是一个嵌套的context!// - 嵌套的user上下文包含name(值为"ZhaoGang")和address(另一个嵌套context!)// - address上下文包含city(值为"beijing")// 键值对用=>分隔,符合MiniJinja 2.12.0的context!宏语法let data_ctx = context! {user => context! {name => "赵刚",address => context! {city => "北京"}}};// 调用模板的render方法,传入上下文数据进行渲染// 渲染过程会将模板中的变量占位符替换为上下文对应的值,并执行过滤器(如upper)// ?操作符处理渲染错误(如变量引用错误、过滤器不存在等)let result = tmpl.render(data_ctx)?;// 打印渲染后的结果// 预期输出:"发现了! ZhaoGang 住在 BEIJING 。"(beijing被upper过滤器转为大写)println!("{}", result);// 主函数正常执行完毕,返回Ok(())Ok(())
}

4. 循环

用 {% for ... %} 遍历数组或迭代器:

// 模板内容
let template = r#"水果列表:{% for fruit in fruits %}- {{ fruit }}{% endfor %}
"#;env.add_template("loop", template)?;// 上下文传入 fruits: vec!["苹果", "香蕉", "橙子"]
// 渲染结果会按列表逐项输出

五、动态表达式

MiniJinja 提供了动态表达式解释执行引擎,用以执行动态逻辑。

// 导入 MiniJinja 的 Environment 模块,用于创建模板环境
use minijinja::Environment;
use minijinja::context;// 定义主函数,返回 Result 类型,可能返回 minijinja::Error 错误
fn main() -> Result<(), minijinja::Error> {// 创建一个新的 MiniJinja 环境实例let env = Environment::new();// 编译表达式:将字符串表达式编译成可执行的表达式对象// 表达式 "a + b * 2 > 10" 包含变量和数学运算let expr = env.compile_expression("a + b * 2 > 10")?;// 对编译后的表达式进行求值:// 传入上下文参数 a=3, b=4// 计算过程:3 + 4 * 2 = 3 + 8 = 11 > 10 → 结果为 truelet result = expr.eval(context! {a => 3,    // 变量 a 的值为 3b => 4     // 变量 b 的值为 4})?;// 断言验证结果是否为真(true)// 因为 11 > 10 成立,所以 result.is_true() 应该返回 trueassert!(result.is_true());// 返回 Ok(()) 表示程序执行成功Ok(())
}

事实上,在模板中也可以用 {{动态表达式}} 的方式来执行动态表达式:

例如,下面代码例子中的 {{user.address.city}} 就是在模板中执行动态表达式:

    let template_str = r#"下面显示{{user.name}}住址:{%if user.address.city=="北京"%}首都{%else%}{{user.address.city}}{%endif%}"#;

六、引入外部函数

尽管在模板和动态表示总可以引入过滤器来提升程序处理能力,但有时我们也需要直接在动态表达式中直接使用外部函数。这时就可以通过注册的方式,将外部函数引入到动态表达式中(包括自定义函数)。

以下代码是将上面的范例中的过滤器变成了函数来实现。注意(env.add_function("fanzhuan", reverse)部分:

// 导入MiniJinja库中的核心组件:
// Environment:用于管理模板环境(存储模板、过滤器等配置)
// context:用于快速创建模板上下文(传递给模板的动态数据)
use minijinja::{Environment, context};// 定义主函数,返回Result类型以优雅处理可能的错误
// 错误类型指定为minijinja::Error,涵盖模板处理中的各类错误(如语法错误、变量缺失等)
fn main() -> Result<(), minijinja::Error> {// 创建一个可变的MiniJinja环境实例// 用mut修饰是因为后续需要向环境中添加模板(修改环境状态)let mut env = Environment::new();// 对自定义函数reverse注册(之后动态表达式以及模板就可以直接使用了!)env.add_function("fanzhuan", reverse);// 向环境中添加一个名为"disp"的模板// 模板内容包含:// - {{ user.name }}:引用上下文user对象的name属性// - {{ fanzhuan(user.address.city) }}:注册函数fanzhuan来对地址内容进行反转。// 使用?操作符处理可能的错误(如模板语法错误),若出错会向上传播env.add_template("disp", "发现了! {{ user.name }} 住在 {{ fanzhuan(user.address.city) }} 。")?;// 从环境中获取名为"disp"的模板实例,用于后续渲染// ?操作符处理可能的错误(如模板不存在)let tmpl = env.get_template("disp")?;// 使用context!宏创建模板上下文(动态数据),采用嵌套结构:// - 顶层键为user,对应的值是一个嵌套的context!// - 嵌套的user上下文包含name(值为"ZhaoGang")和address(另一个嵌套context!)// - address上下文包含city(值为"beijing")// 键值对用=>分隔,符合MiniJinja 2.12.0的context!宏语法let data_ctx = context! {user => context! {name => "赵刚",address => context! {city => "北京"}}};// 调用模板的render方法,传入上下文数据进行渲染// 渲染过程会将模板中的变量占位符替换为上下文对应的值,并执行过滤器(如upper)// ?操作符处理渲染错误(如变量引用错误、过滤器不存在等)let result = tmpl.render(data_ctx)?;// 打印渲染后的结果// 预期输出:"发现了! ZhaoGang 住在 BEIJING 。"(beijing被upper过滤器转为大写)println!("{}", result);// 主函数正常执行完毕,返回Ok(())Ok(())
}// 定义一个反转字符串的函数
fn reverse(s: &str) -> String {s.chars().rev().collect()
}

七、模板的继承和拼接

MiniJinja 的模板继承和拼接是实现模板复用、减少重复代码的核心机制,二者分别解决不同场景的复用需求:继承用于 “层级化的模板结构复用”(如基础布局与具体页面的关系),拼接用于 “碎片化的模板片段复用”(如通用组件、重复出现的小模块)。

(一)模板继承(Template Inheritance)

核心目的是:定义一个 “基础模板(父模板)” 作为骨架,包含通用结构(如页面布局、导航栏、页脚),然后让 “子模板” 继承这个骨架,只填充或覆盖特定部分。

核心语法:

  • {% extends "parent.html" %}:子模板声明继承自哪个父模板。
  • {% block 名称 %}:父模板中定义 “可被覆盖的区域”,子模板通过同名block覆盖或补充内容。
  • {{ super() }}:子模板中调用父模板对应block的原始内容(用于 “补充” 而非 “完全覆盖”)。

父模板示例(base.html):定义基础布局,包含固定的头部、底部,以及一个可替换的内容区:

// 导入MiniJinja库中的Environment模块,用于创建和管理模板环境
use minijinja::{Environment};// 主函数,返回Result类型用于错误处理
fn main() -> Result<(), minijinja::Error> {// 创建一个新的模板环境实例// mut关键字表示这个环境变量是可变的,因为后面需要添加模板let mut env = Environment::new();// 定义基础模板的字符串内容,使用原始字符串语法(r#"..."#)避免转义let base_str = r#"<!-- base.html --><!DOCTYPE html><html><head><!-- 定义可被子模板覆盖的title区块,提供默认值 --><title>{% block title %}默认标题{% endblock %}</title></head><body><header>通用导航栏</header><!-- 可被子模板覆盖的内容区 --><!-- 定义content区块,子模板可以覆盖此内容 -->{% block content %}<p>父模板的默认内容输出!</p>{% endblock %}<footer>通用页脚</footer></body></html>"#;// 定义子模板的字符串内容let home_str = r#"<!-- home.html --><!-- 声明此模板继承自base.html模板 -->{% extends "base.html" %}<!-- 覆盖标题区块 --><!-- 重写title区块的内容,替换基模板中的默认标题 -->{% block title %}首页{% endblock %}<!-- 这个信息没有写在block中,不会被渲染 -->{% block content %}<h1>欢迎来到首页</h1><p>这是Home页独有的内容</p>{% endblock %}"#;// 将基础模板字符串添加到模板环境中,命名为"base.html"// ?操作符用于错误传播:如果添加失败,直接返回错误env.add_template("base.html", base_str)?;// 将子模板字符串添加到模板环境中,命名为"home.html"env.add_template("home.html", home_str)?;// 从模板环境中获取名为"home.html"的模板实例let tmpl = env.get_template("home.html")?;// 渲染模板,传入空的上下文对象(因为没有数据需要传递)// context!{}宏创建一个空的上下文字典let result = tmpl.render(&minijinja::context! {})?;// 打印渲染后的HTML结果到控制台println!("{}", result);// 程序正常结束,返回Ok(())表示成功Ok(())
}

结果:

        <!-- home.html --><!-- 声明此模板继承自base.html模板 --><!-- base.html --><!DOCTYPE html><html><head><!-- 定义可被子模板覆盖的title区块,提供默认值 --><title>首页</title></head><body><header>通用导航栏</header><!-- 可被子模板覆盖的内容区 --><!-- 定义content区块,子模板可以覆盖此内容 --><h1>欢迎来到首页</h1><p>这是Home页独有的内容</p><footer>通用页脚</footer></body></html>

注意:当子模板开始声明继承父模板开始(“{% extends "base.html" %}”之后),子模板就要基于父模板来显示了,子模板如果有超出父模板可覆盖区域({% block 名称 %}声明部分)之外的部分,将被忽略。也就是说:在{% extends "base.html" %}开始之后,子模板都会处于“继承父模板”的状态,一直到子模板结束。

关键特性:
  • 单继承:一个子模板只能继承一个父模板(避免复杂的多继承逻辑)。
  • 区块嵌套:父模板的block中可以嵌套其他block,子模板可按需逐层覆盖。
  • super() 复用:若子模板不想完全覆盖父模板的区块,可通过{{ super() }}保留原始内容,例如:
// 导入MiniJinja库中的Environment模块,用于创建和管理模板环境
use minijinja::{Environment};// 主函数,返回Result类型用于错误处理
fn main() -> Result<(), minijinja::Error> {// 创建一个新的模板环境实例// mut关键字表示这个环境变量是可变的,因为后面需要添加模板let mut env = Environment::new();// 定义基础模板的字符串内容,使用原始字符串语法(r#"..."#)避免转义let base_str = r#"<!-- base.html --><!DOCTYPE html><html><head><!-- 定义可被子模板覆盖的title区块,提供默认值 --><title>{% block title %}默认标题{% endblock %}</title></head><body><header>通用导航栏</header><!-- 可被子模板覆盖的内容区 --><!-- 定义content区块,子模板可以覆盖此内容 -->{% block content %}{% block content_title %}默认内容标题{% endblock %}<p>父模板的默认内容输出!</p>{% endblock %}<footer>通用页脚</footer></body></html>"#;// 定义子模板的字符串内容let home_str = r#"<!-- home.html --><!-- 声明此模板继承自base.html模板 -->{% extends "base.html" %}<!-- 覆盖标题区块 --><!-- 重写title区块的内容,替换基模板中的默认标题 -->{% block title %}首页{% endblock %}<!-- 这个信息没有写在block中,不会被渲染 -->{% block content %}{{super()}}<h1>欢迎来到首页</h1><p>这是Home页独有的内容</p>{% endblock %}"#;// 将基础模板字符串添加到模板环境中,命名为"base.html"// ?操作符用于错误传播:如果添加失败,直接返回错误env.add_template("base.html", base_str)?;// 将子模板字符串添加到模板环境中,命名为"home.html"env.add_template("home.html", home_str)?;// 从模板环境中获取名为"home.html"的模板实例let tmpl = env.get_template("home.html")?;// 渲染模板,传入空的上下文对象(因为没有数据需要传递)// context!{}宏创建一个空的上下文字典let result = tmpl.render(&minijinja::context! {})?;// 打印渲染后的HTML结果到控制台println!("{}", result);// 程序正常结束,返回Ok(())表示成功Ok(())
}

结果:

        <!-- home.html --><!-- 声明此模板继承自base.html模板 --><!-- base.html --><!DOCTYPE html><html><head><!-- 定义可被子模板覆盖的title区块,提供默认值 --><title>首页</title></head><body><header>通用导航栏</header><!-- 可被子模板覆盖的内容区 --><!-- 定义content区块,子模板可以覆盖此内容 -->默认内容标题<p>父模板的默认内容输出!</p><h1>欢迎来到首页</h1><p>这是Home页独有的内容</p><footer>通用页脚</footer></body></html>

(二)模板拼接(Template Inclusion)

核心目的是:将独立的 “模板片段”(如导航组件、卡片模块、按钮组)嵌入到其他模板中,实现碎片化复用(类似 “组件引入”)。

核心语法:

  {% include "fragment.html" %}:在当前模板中嵌入指定的模板片段。

例如:

// 导入 MiniJinja 库中的 Environment 类型,用于创建和管理模板环境
use minijinja::{Environment};// 主函数,返回 Result 类型用于错误处理,错误类型为 minijinja::Error
fn main() -> Result<(), minijinja::Error> {// 创建一个新的模板环境实例// mut 关键字表示这个环境变量是可变的,因为后面需要通过 add_template 方法添加模板let mut env = Environment::new();// 定义主页面模板的字符串内容,使用原始字符串语法(r#"..."#)避免转义特殊字符let home_str = r#"<!-- home.html 主页面模板 --><!DOCTYPE html><html><body><!-- 引入列表片段模板 --><h1>首页内容</h1><!-- 使用 include 指令引入 list.html 模板 --><!-- MiniJinja 的 include 指令不支持 with 参数传递,变量通过上下文传递 -->{% include "list.html"%}</body></html>"#;// 定义列表片段模板的字符串内容let list_str = r#"<!-- list.html 列表片段模板 --><!-- 这个模板接收 winner 变量,用于判断哪个条目应该添加 winner class --><!-- 使用 if 条件判断来为冠军条目添加特殊的 CSS 类 --><p {% if winner=="胡大民"%} class="winner" {% endif%}>胡大民</p><p {% if winner=="赵刚"%} class="winner" {% endif%}>赵刚</p><p {% if winner=="李明"%} class="winner" {% endif%}>李明</p>"#;    // 将主页面模板字符串添加到模板环境中,命名为 "home.html"// ? 操作符用于错误传播:如果添加失败,直接返回错误env.add_template("home.html", home_str)?;// 将列表片段模板字符串添加到模板环境中,命名为 "list.html"env.add_template("list.html", list_str)?;// 从模板环境中获取名为 "home.html" 的模板实例let tmpl = env.get_template("home.html")?;// 渲染模板,传入包含 winner 变量的上下文对象// context!{} 宏创建一个包含键值对的上下文字典// 这里传递 winner 变量,值为 "赵刚",这样 list.html 模板中的条件判断就能正常工作let result = tmpl.render(&minijinja::context! {winner => "赵刚"  // 设置冠军为"赵刚",对应的<p>标签会添加 class="winner"})?;// 打印渲染后的HTML结果到控制台println!("{}", result);// 程序正常执行完毕,返回 Ok(()) 表示成功Ok(())
}

结果:

        <!-- home.html 主页面模板 --><!DOCTYPE html><html><body><!-- 引入列表片段模板 --><h1>首页内容</h1><!-- 使用 include 指令引入 list.html 模板 --><!-- MiniJinja 的 include 指令不支持 with 参数传递,变量通过上下文传递 --><!-- list.html 列表片段模板 --><!-- 这个模板接收 winner 变量,用于判断哪个条目应该添加 winner class --><!-- 使用 if 条件判断来为冠军条目添加特殊的 CSS 类 --><p >胡大民</p><p  class="winner" >赵刚</p><p >李明</p></body></html>
关键特性:
  • 片段独立性:被引入的模板片段可单独维护,修改后所有引用它的模板都会生效。
  • 条件引入:可结合if判断是否引入片段,例如:{% if user.is_login %}{% include "user_menu.html" %}{% endif %}

(三)继承 vs 拼接:核心区别

维度模板继承模板拼接
关系父与子的 “扩展关系”(is-a)主与次的 “嵌入关系”(has-a)
用途复用整体布局(如页面框架)复用局部片段(如组件、小模块)
灵活性子模板仅需关注差异部分片段可在任意模板中被多次引入
语法核心extends + block + super()include + 可选参数传递

八、错误处理

MiniJinja提供了完整的模板、动态表达式等相关错误链。以下程序模拟模板中存在错误:

// 导入 MiniJinja 库中的 Environment 类型,用于创建和管理模板环境
use minijinja::Environment;// 主函数,返回 Result 类型用于错误处理,错误类型为 minijinja::Error
fn main() -> Result<(), minijinja::Error> {// 创建一个新的模板环境实例s// mut 关键字表示这个环境变量是可变的,因为后面需要通过 add_template 方法添加模板let mut env = Environment::new();// 定义主页面模板的字符串内容,使用原始字符串语法(r#"..."#)避免转义特殊字符let home_str = r#"<!-- home.html 主页面模板 --><!DOCTYPE html><html><body><!-- 引入列表片段模板 --><h1>首页内容</h1><!-- 使用 include 指令引入 list.html 模板 --><!-- MiniJinja 的 include 指令不支持 with 参数传递,变量通过上下文传递 -->{% include "list.html"%}</body></html>"#;// 定义列表片段模板的字符串内容// 注意:这里故意使用了错误的语法 {% iff %} 来测试错误处理let list_str = r#"<!-- list.html 列表片段模板 --><!-- 这个模板接收 winner 变量,用于判断哪个条目应该添加 winner class --><!-- 使用 if 条件判断来为冠军条目添加特殊的 CSS 类 --><p {% iff winner=="胡大民"%} class="winner" {% endif%}>胡大民</p><p {% if winner=="赵刚"%} class="winner" {% endif%}>赵刚</p><p {% if winner=="李明"%} class="winner" {% endif%}>李明</p>"#;    // 将主页面模板字符串添加到模板环境中,命名为 "home.html"// 使用 match 处理可能的错误,但不传播错误match env.add_template("home.html", home_str) {Ok(_) => (),Err(e) => println!("添加 home.html 模板失败: {}", e),};// 将列表片段模板字符串添加到模板环境中,命名为 "list.html"// 使用 match 处理可能的错误,但不传播错误match env.add_template("list.html", list_str) {Ok(_) => (),Err(e) => println!("添加 list.html 模板失败: {}", e),};// 尝试从模板环境中获取名为 "home.html" 的模板实例// 使用 match 处理可能的错误,但不传播错误let result = match env.get_template("home.html") {Ok(tmpl) => {// 渲染模板,传入包含 winner 变量的上下文对象// context!{} 宏创建一个包含键值对的上下文字典// 这里传递 winner 变量,值为 "赵刚",这样 list.html 模板中的条件判断就能正常工作tmpl.render(&minijinja::context! {winner => "赵刚"  // 设置冠军为"赵刚",对应的<p>标签会添加 class="winner"})},Err(e) => {println!("获取模板失败: {}", e);// 返回一个错误结果,但会在下面的 match 中处理Err(e)},};// 使用 match 表达式处理渲染结果// 注意:不再返回错误,而是打印错误信息后返回 Ok(()),用来模拟主线程不受错误影响而崩溃match result {Ok(r) => {// 打印渲染后的HTML结果到控制台println!("{}", r);},Err(e) => {// 打印详细的错误信息,但不传播错误println!("模板渲染失败:{}", e);},}// 无论模板处理是否成功,都返回 Ok(()) 表示主函数正常执行完毕Ok(())
}

结果:

添加 list.html 模板失败: syntax error: unknown statement iff (in list.html:5)
模板渲染失败:template not found: tried to include non-existing template "list.html" (in home.html:10)

完善的提示出了出错的模板名称、出错行,当然也可以细化追踪具体的错误类型(此处略)。

http://www.dtcms.com/a/355605.html

相关文章:

  • Docker 核心技术:Union File System
  • 知微集:梯度下降详解
  • 编写TreeMap自定义排序的插曲
  • 信号量使用流程
  • 多媒体内容智能检索技术进展
  • [特殊字符] ​​MySQL性能参数查询总结​
  • 146-延长无线传感器网络生命周期的睡眠调度机制的混合元启发式优化方法!
  • [RK3576][Android14] Android->添加以太网MAC地址选项
  • Spring Boot 实战:接入 DeepSeek API 实现问卷文本优化
  • FFmpeg 实战:从零开始写一个简易视频播放器
  • 视频层和叠加层
  • 数据结构:冒泡排序 (Bubble Sort)
  • Android14 USB子系统的启动以及动态切换相关的init.usb.rc详解
  • mysql主从复制GTID模式
  • Day16_【机器学习—模型拟合问题】
  • AI智能能源管理系统深度剖析:从工业节能到数据中心降耗,解锁企业降本减排新方案
  • Docker 40个自动化管理脚本-1 (20/40)
  • CVPR2019 Oral论文《Side Window Filtering》解读及算法 Python 实现
  • 网络编程3-UDP协议
  • Megatron-Energon 和 Megatron-LM
  • 解锁五大联赛数据:API技术指南
  • Python在AI与数据科学工作流中的新角色:2025年实践指南
  • X-AnyLabeling:Win10上安装使用X-AnyLabeling标注工具
  • 国内永久免费云服务器有哪些?
  • AI视频生成工具全景对比:元宝AI、即梦AI、清影AI和Vidu AI
  • Java学习笔记之——通过分页查询样例感受JDBC、Mybatis以及MybatisPlus(一)
  • CPU、进程、线程上下文切换
  • 使用Rag 命中用户feedback提升triage agent 准确率
  • 特斯拉 Tesla FSD 12.6.4,FSD14, VLA 和 华为 ADS 4.0 比较
  • 广东省省考备考(第八十九天8.28)——判断推理(第九节课)