MiniJinja 是 Rust 生态中一款轻量、高效的模板引擎,语法贴近 Python 的 Jinja2,适合快速实现动态文本生成。本教程基于最新的 2.12.0 版本,将带你快速掌握其核心用法。
一、准备工作
安装依赖
在 Cargo.toml
中添加最新版本依赖:
bash
[dependencies]
minijinja = "2.12.0" # 最新稳定版本
二、核心概念
- Environment(环境):模板引擎的核心容器,管理模板、过滤器、全局配置等。
- 模板(Template):包含静态文本和动态占位符的字符串,定义输出格式。
- 上下文(Context):传递给模板的动态数据(键值对),模板通过占位符引用。
- 过滤器(Filter) :对模板数据进行加工的函数,语法为
{``{ 变量 | 过滤器 }}
。
三、快速入门:第一个模板
示例代码
rust
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(())
}
结果:
bash
Hello, Alice! You are 30 years old.
四、模板语法基础
1. 变量引用
用 {``{ 变量名 }}
引用上下文数据,支持嵌套结构:
rust
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 => value
let 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 转换为大写。
rust
// 导入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("过滤器名字", 普通函数的名字);
rust
// 导入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过滤器起了作用!
bash
发现了! 赵刚 住在 北京北京北京 。
(2)注册自定义函数为过滤器
先自定义函数,之后同上一样,将其注册为过滤器。
rust
// 导入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()
}
结果:
rust
发现了! 赵刚 住在 京北 。
3. 条件判断
用 {% if ... %}
语法实现条件渲染:
rust
// 导入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 ... %}
遍历数组或迭代器:
rust
// 模板内容
let template = r#"
水果列表:
{% for fruit in fruits %}
- {{ fruit }}
{% endfor %}
"#;
env.add_template("loop", template)?;
// 上下文传入 fruits: vec!["苹果", "香蕉", "橙子"]
// 渲染结果会按列表逐项输出
五、动态表达式
MiniJinja 提供了动态表达式解释执行引擎,用以执行动态逻辑。
rust
// 导入 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 → 结果为 true
let result = expr.eval(context! {
a => 3, // 变量 a 的值为 3
b => 4 // 变量 b 的值为 4
})?;
// 断言验证结果是否为真(true)
// 因为 11 > 10 成立,所以 result.is_true() 应该返回 true
assert!(result.is_true());
// 返回 Ok(()) 表示程序执行成功
Ok(())
}
事实上,在模板中也可以用 {{动态表达式}} 的方式来执行动态表达式:
例如,下面代码例子中的 {{user.address.city}} 就是在模板中执行动态表达式:
rust
let template_str = r#"
下面显示{{user.name}}住址:
{%if user.address.city=="北京"%}
首都
{%else%}
{{user.address.city}}
{%endif%}
"#;
六、引入外部函数
尽管在模板和动态表示总可以引入过滤器来提升程序处理能力,但有时我们也需要直接在动态表达式中直接使用外部函数。这时就可以通过注册的方式,将外部函数引入到动态表达式中(包括自定义函数)。
以下代码是将上面的范例中的过滤器 变成了函数来实现。注意(env.add_function("fanzhuan", reverse)部分:
rust
// 导入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):定义基础布局,包含固定的头部、底部,以及一个可替换的内容区:
rust
// 导入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(())
}
结果:
bash
<!-- 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() }}
保留原始内容,例如:
rust
// 导入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(())
}
结果:
bash
<!-- 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" %}
:在当前模板中嵌入指定的模板片段。
例如:
rust
// 导入 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(())
}
结果:
rust
<!-- 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提供了完整的模板、动态表达式等相关错误链。以下程序模拟模板中存在错误:
rust
// 导入 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(())
}
结果:
bash
添加 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)
完善的提示出了出错的模板名称、出错行,当然也可以细化追踪具体的错误类型(此处略)。