Rust Web开发指南 第六章(动态网页模板技术-MiniJinja速成教程)
MiniJinja 是 Rust 生态中一款轻量、高效的模板引擎,语法贴近 Python 的 Jinja2,适合快速实现动态文本生成。本教程基于最新的 2.12.0 版本,将带你快速掌握其核心用法。
一、准备工作
安装依赖
在 Cargo.toml
中添加最新版本依赖:
[dependencies]
minijinja = "2.12.0" # 最新稳定版本
二、核心概念
- Environment(环境):模板引擎的核心容器,管理模板、过滤器、全局配置等。
- 模板(Template):包含静态文本和动态占位符的字符串,定义输出格式。
- 上下文(Context):传递给模板的动态数据(键值对),模板通过占位符引用。
- 过滤器(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)
完善的提示出了出错的模板名称、出错行,当然也可以细化追踪具体的错误类型(此处略)。