模板渲染 #
模板引擎概述 #
Actix Web 支持多种模板引擎:
| 模板引擎 | 特点 | 适用场景 |
|---|---|---|
| Tera | Jinja2 风格,功能丰富 | 动态网站 |
| Askama | 编译时模板,高性能 | 性能敏感场景 |
| Handlebars | JavaScript 风格 | 前端开发者友好 |
| Askama | Rust 模板,类型安全 | 类型安全要求高 |
Tera 模板引擎 #
添加依赖 #
toml
[dependencies]
actix-web = "4"
tera = "1"
serde = { version = "1", features = ["derive"] }
基本配置 #
rust
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use tera::{Context, Tera};
async fn index(tera: web::Data<Tera>) -> impl Responder {
let mut context = Context::new();
context.insert("title", "Welcome");
context.insert("name", "World");
let rendered = tera.render("index.html", &context).unwrap();
HttpResponse::Ok()
.content_type("text/html")
.body(rendered)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let tera = Tera::new("templates/**/*.html").unwrap();
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(tera.clone()))
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
模板文件 #
templates/index.html:
html
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
传递数据 #
rust
use serde::Serialize;
#[derive(Serialize)]
struct User {
name: String,
email: String,
}
async fn user_profile(tera: web::Data<Tera>) -> impl Responder {
let user = User {
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
let mut context = Context::new();
context.insert("user", &user);
let rendered = tera.render("profile.html", &context).unwrap();
HttpResponse::Ok()
.content_type("text/html")
.body(rendered)
}
模板 templates/profile.html:
html
{% extends "base.html" %}
{% block content %}
<h1>User Profile</h1>
<p>Name: {{ user.name }}</p>
<p>Email: {{ user.email }}</p>
{% endblock %}
基础模板 #
templates/base.html:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2024 My Website</p>
</footer>
</body>
</html>
循环和条件 #
rust
async fn users(tera: web::Data<Tera>) -> impl Responder {
let users = vec![
User { name: "Alice".to_string(), email: "alice@example.com".to_string() },
User { name: "Bob".to_string(), email: "bob@example.com".to_string() },
];
let mut context = Context::new();
context.insert("users", &users);
context.insert("is_admin", &true);
let rendered = tera.render("users.html", &context).unwrap();
HttpResponse::Ok()
.content_type("text/html")
.body(rendered)
}
模板 templates/users.html:
html
{% extends "base.html" %}
{% block title %}Users{% endblock %}
{% block content %}
<h1>Users List</h1>
{% if is_admin %}
<a href="/users/new">Add New User</a>
{% endif %}
<ul>
{% for user in users %}
<li>
<strong>{{ user.name }}</strong>
<span>{{ user.email }}</span>
</li>
{% endfor %}
</ul>
{% if users | length == 0 %}
<p>No users found.</p>
{% endif %}
{% endblock %}
Askama 模板引擎 #
添加依赖 #
toml
[dependencies]
actix-web = "4"
askama = "0.12"
定义模板 #
rust
use askama::Template;
#[derive(Template)]
#[template(path = "hello.html")]
struct HelloTemplate<'a> {
name: &'a str,
}
async fn hello_askama() -> impl Responder {
let template = HelloTemplate { name: "World" };
HttpResponse::Ok()
.content_type("text/html")
.body(template.render().unwrap())
}
模板 templates/hello.html:
html
<!DOCTYPE html>
<html>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
条件和循环 #
rust
#[derive(Template)]
#[template(path = "users.html")]
struct UsersTemplate {
users: Vec<User>,
is_admin: bool,
}
#[derive(Clone)]
struct User {
name: String,
email: String,
}
async fn users_askama() -> impl Responder {
let template = UsersTemplate {
users: vec![
User { name: "Alice".to_string(), email: "alice@example.com".to_string() },
User { name: "Bob".to_string(), email: "bob@example.com".to_string() },
],
is_admin: true,
};
HttpResponse::Ok()
.content_type("text/html")
.body(template.render().unwrap())
}
模板 templates/users.html:
html
<!DOCTYPE html>
<html>
<body>
<h1>Users</h1>
{% if is_admin %}
<a href="/users/new">Add User</a>
{% endif %}
<ul>
{% for user in users %}
<li>{{ user.name }} - {{ user.email }}</li>
{% endfor %}
</ul>
</body>
</html>
Handlebars 模板引擎 #
添加依赖 #
toml
[dependencies]
actix-web = "4"
handlebars = "5"
serde_json = "1"
基本配置 #
rust
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use handlebars::Handlebars;
use serde_json::json;
async fn index_hbs(hbs: web::Data<Handlebars<'_>>) -> impl Responder {
let data = json!({
"title": "Welcome",
"name": "World"
});
let rendered = hbs.render("index", &data).unwrap();
HttpResponse::Ok()
.content_type("text/html")
.body(rendered)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mut hbs = Handlebars::new();
hbs.register_template_file("index", "templates/index.hbs").unwrap();
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(hbs.clone()))
.route("/", web::get().to(index_hbs))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
模板 templates/index.hbs:
html
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<h1>Hello, {{name}}!</h1>
</body>
</html>
自定义模板响应器 #
rust
use actix_web::{HttpRequest, HttpResponse, Responder};
use tera::{Context, Tera};
struct HtmlTemplate {
template: String,
context: Context,
}
impl HtmlTemplate {
fn new(template: &str, context: Context) -> Self {
Self {
template: template.to_string(),
context,
}
}
}
impl Responder for HtmlTemplate {
type Body = String;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
let tera = req.app_data::<web::Data<Tera>>()
.expect("Tera not configured");
match tera.render(&self.template, &self.context) {
Ok(html) => HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(html),
Err(e) => HttpResponse::InternalServerError()
.body(format!("Template error: {}", e)),
}
}
}
async fn index_auto() -> impl Responder {
let mut context = Context::new();
context.insert("title", "Welcome");
HtmlTemplate::new("index.html", context)
}
错误处理 #
rust
use tera::Error as TeraError;
fn render_template(
tera: &Tera,
template: &str,
context: &Context,
) -> Result<String, actix_web::Error> {
tera.render(template, context)
.map_err(|e| {
match e {
TeraError::TemplateNotFound(_) => {
actix_web::error::ErrorNotFound("Template not found")
}
_ => actix_web::error::ErrorInternalServerError(e),
}
})
}
async fn safe_render(tera: web::Data<Tera>) -> impl Responder {
let mut context = Context::new();
context.insert("title", "Welcome");
match render_template(&tera, "index.html", &context) {
Ok(html) => HttpResponse::Ok()
.content_type("text/html")
.body(html),
Err(e) => e.into(),
}
}
完整示例 #
rust
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::Serialize;
use tera::{Context, Tera};
#[derive(Serialize, Clone)]
struct User {
id: u32,
name: String,
email: String,
}
async fn index(tera: web::Data<Tera>) -> impl Responder {
let mut context = Context::new();
context.insert("title", "Home");
let html = tera.render("index.html", &context).unwrap();
HttpResponse::Ok().content_type("text/html").body(html)
}
async fn users(tera: web::Data<Tera>) -> impl Responder {
let users = vec![
User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
];
let mut context = Context::new();
context.insert("title", "Users");
context.insert("users", &users);
let html = tera.render("users.html", &context).unwrap();
HttpResponse::Ok().content_type("text/html").body(html)
}
async fn user_detail(
path: web::Path<u32>,
tera: web::Data<Tera>,
) -> impl Responder {
let user = User {
id: path.into_inner(),
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
let mut context = Context::new();
context.insert("title", &format!("User - {}", user.name));
context.insert("user", &user);
let html = tera.render("user_detail.html", &context).unwrap();
HttpResponse::Ok().content_type("text/html").body(html)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let tera = Tera::new("templates/**/*.html").unwrap();
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(tera.clone()))
.route("/", web::get().to(index))
.route("/users", web::get().to(users))
.route("/users/{id}", web::get().to(user_detail))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
下一步 #
现在你已经掌握了模板渲染,继续学习 静态文件,深入了解静态资源服务!
最后更新:2026-03-29