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

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

一、准备工作

安装依赖

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

bash 复制代码
[dependencies]
minijinja = "2.12.0"  # 最新稳定版本

二、核心概念

  1. Environment(环境):模板引擎的核心容器,管理模板、过滤器、全局配置等。
  2. 模板(Template):包含静态文本和动态占位符的字符串,定义输出格式。
  3. 上下文(Context):传递给模板的动态数据(键值对),模板通过占位符引用。
  4. 过滤器(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)

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

相关推荐
林太白2 分钟前
npm多组件发布Vue3+TS版本,快来像Antd一样搭建属于你的UI库吧
前端·javascript·node.js
Juchecar10 分钟前
如何避免Node.js项目node_modules重复占用空间
前端
百罹鸟21 分钟前
nestjs 从零开始 一步一步实践
前端·node.js·nestjs
袁煦丞28 分钟前
Wiki.js团队知识大脑/个人笔记管家:cpolar内网穿透实验室第496个成功挑战
前端·程序员·远程工作
大飞pkz33 分钟前
【Lua】题目小练12
开发语言·lua·题目小练
赵得C38 分钟前
Java 多线程环境下的全局变量缓存实践指南
java·开发语言·后端·spring·缓存
维他AD钙1 小时前
2025 年前端性能优化实战:从加载到渲染的全链路优化指南
前端
nightunderblackcat1 小时前
新手向:Python编写简易翻译工具
开发语言·python