HTML 宏高级特性
html! 宏是 euv 模板系统的核心。虽然其用于创建元素和文本节点的基本语法很直观,但 euv 提供了几个高级特性来实现复杂、动态的用户界面:条件渲染、响应式属性值、内联样式、布尔属性和事件委托。本文将详细探讨每个特性。
快速回顾:基本 html! 语法
html! 宏使用类似 Rust 的语法来描述虚拟 DOM 节点:
html! {
div {
h1 { "Hello, World!" }
p { "This is a paragraph." }
}
}
这会创建一个虚拟 DOM 树,euv 的渲染器会将其高效地映射到真实 DOM 元素。宏支持嵌套、文本内容和子组件。
使用 if/else 进行条件渲染
euv 的 html! 宏通过 if/else 块支持条件渲染。关键规则:else 分支不能省略。如果需要一个空分支,使用空字符串 ""。
基本 if/else
html! {
if is_logged_in {
div { "Welcome back!" }
} else {
div { "Please log in." }
}
}
空分支
当你需要一个不渲染任何内容的分支时:
html! {
if show_banner {
div { "Special offer!" }
} else {
""
}
}
""(空字符串)会渲染为 Empty 虚拟节点变体,不产生任何 DOM 输出。
链式条件
你可以链式连接多个条件:
html! {
if state == 0 {
div { "Idle" }
} else if state == 1 {
div { "Loading..." }
} else if state == 2 {
div { "Complete" }
} else {
div { "Unknown state" }
}
}
使用 match 进行条件渲染
对于更复杂的分支逻辑,euv 在 html! 中提供了 match。关键规则:match 必须始终包含一个 _ 通配分支。
基本 match
html! {
match page {
"home" => { div { "Home Page" } },
"about" => { div { "About Page" } },
"contact" => { div { "Contact Page" } },
_ => { div { "404 Not Found" } }
}
}
_ 通配分支是强制性的。它捕获所有未显式处理的情况。没有它,代码将无法编译。
复杂模式匹配
html! {
match user_role {
"admin" => { div { "Admin Dashboard" } },
"editor" => { div { "Editor Panel" } },
"viewer" => { div { "View Only" } },
_ => { div { "Unauthorized" } }
}
}
使用 for 循环渲染列表
html! 宏支持 for 循环来渲染列表。关键规则:迭代表达式必须用花括号 {} 包裹。
基本 for 循环
html! {
for item in {items} {
li { item.name }
}
}
{items} 语法告诉 euv items 是一个响应式表达式。当 items 信号变化时,列表会自动重新渲染。
渲染购物列表
let cart_items: Signal<Vec<String>> = use_signal(|| vec!["Apple", "Banana", "Cherry"]);
html! {
ul {
for item in {cart_items} {
li { item }
}
}
}
嵌套循环
html! {
for category in {categories} {
div {
h3 { category.name }
for product in {category.products} {
div { product.name }
}
}
}
}
响应式属性值
html! 宏支持响应式属性值。当信号值变化时,对应的 DOM 属性会自动更新。
基本响应式属性
let is_disabled: Signal<bool> = use_signal(|| false);
html! {
button {
disabled: is_disabled
"Submit"
}
}
动态类名
let active_tab: Signal<String> = use_signal(|| "home".to_string());
html! {
div {
class: active_tab
"Tab Content"
}
}
样式属性
你可以响应式地设置样式属性:
let bg_color: Signal<String> = use_signal(|| "blue".to_string());
html! {
div {
style: "background-color: {bg_color}"
"Colored Box"
}
}
内联样式
除了响应式属性,euv 在 html! 宏中直接支持内联样式。你可以混合使用静态和动态样式值:
let font_size: Signal<String> = use_signal(|| "14px".to_string());
html! {
div {
style: "padding: 10px; font-size: {font_size}; border: 1px solid #ccc"
"Styled content"
}
}
静态值直接书写,而响应式值使用 {signal_name} 语法。
布尔属性
HTML 布尔属性如 disabled、checked、readonly 和 required 在 euv 中自然处理:
let is_disabled: Signal<bool> = use_signal(|| false);
let is_checked: Signal<bool> = use_signal(|| true);
html! {
div {
input {
type: "checkbox"
checked: is_checked
}
button {
disabled: is_disabled
"Submit"
}
}
}
当信号值为 true 时,属性存在于 DOM 中。当为 false 时,它被移除。
事件处理
euv 在 html! 中提供了全面的事件处理系统。有几种不同的模式适用于不同的用例。
基于闭包的处理器
最灵活的方法是使用闭包:
button {
onclick: move |event: Event| {
count.set(count.get() + 1);
}
"Click"
}
use_toggle 辅助函数
对于简单的切换行为:
let is_active: Signal<bool> = use_signal(|| false);
button {
onclick: use_toggle(is_active)
"Toggle"
}
输入值辅助函数
euv 为表单输入提供了专门的辅助函数:
let input_text: Signal<String> = use_signal(|| "".to_string());
let checkbox_checked: Signal<bool> = use_signal(|| false);
html! {
div {
input {
oninput: on_input_value(input_text)
}
input {
onchange: on_change_value(input_text)
}
input {
type: "checkbox"
onchange: on_change_checked(checkbox_checked)
}
}
}
on_input_value(signal)— 每次按键时更新信号。on_change_value(signal)— 在失焦或回车时更新信号。on_change_checked(signal)— 在复选框切换时更新布尔信号。
NativeEventHandler
对于更精细的控制,可以使用 NativeEventHandler::create:
NativeEventHandler::create("click", move |_event: Event| {
// 处理事件
})
这让你可以直接访问原始事件对象,适用于自定义事件委托等高级场景。
事件委托
euv 的渲染器在底层使用事件委托。euv 不在每个元素上附加单独的事件监听器,而是在根级别附加单个监听器,根据虚拟 DOM 树来分发事件。这具有重要的性能优势:
- 内存效率:更少的事件监听器意味着更少的内存使用。
- 动态内容:通过
for循环添加的新元素自动继承事件处理,无需重新附加监听器。 - 一致性:所有元素的事件行为统一。
你不需要做任何特殊操作来从事件委托中受益——它在 euv 的渲染引擎中默认启用。
嵌套组件
html! 宏支持在组件内部嵌套其他组件:
#[derive(Clone, Default)]
struct MyCardProps {
title: &'static str,
onclick: Option<Rc<dyn Fn(Event)>>,
disabled: Signal<bool>
}
#[component]
pub fn my_card(node: VirtualNode<MyCardProps>) -> VirtualNode {
let MyCardProps { title, .. } = node.try_get_props().unwrap_or_default();
let children: VirtualNode = node.try_get_child_node();
html! {
div {
h3 { title }
children
}
}
}
使用方式:
html! {
my_card {
title: "My Card"
"This is the card content."
}
}
组件通过 try_get_props() 方法接收属性,通过 try_get_child_node() 接收子节点。#[component] 属性标记一个函数为组件工厂。
实用示例
示例 1:动态导航栏
let current_page: Signal<String> = use_signal(|| "home".to_string());
html! {
nav {
for page in {pages} {
a {
class: if page.id == current_page { "active" } else { "" }
onclick: move |_| { current_page.set(page.id.clone()) }
page.label
}
}
}
}
示例 2:条件表单字段
let form_type: Signal<String> = use_signal(|| "personal".to_string());
html! {
form {
input { type: "text"; placeholder: "Name" }
match form_type.as_str() {
"personal" => {
input { type: "email"; placeholder: "Personal Email" }
},
"business" => {
div {
input { type: "email"; placeholder: "Business Email" }
input { type: "text"; placeholder: "Company Name" }
}
},
_ => { "" }
}
button { type: "submit" "Submit" }
}
}
示例 3:响应式数据表格
let rows: Signal<Vec<RowData>> = use_signal(|| vec![]);
let sort_column: Signal<String> = use_signal(|| "name".to_string());
html! {
table {
thead {
tr {
for col in {columns} {
th {
onclick: move |_| { sort_column.set(col.key.clone()) }
col.label
}
}
}
}
tbody {
for row in {rows} {
tr {
for col in {columns} {
td { row.get_field(col.key) }
}
}
}
}
}
}
总结
if/else实现条件渲染。else分支是强制性的;使用""表示空分支。match提供基于模式的条件渲染。必须有_通配分支。for循环渲染列表。迭代表达式必须用{}包裹。- 响应式属性 在信号变化时自动更新 DOM。
- 内联样式 支持静态和动态值,使用
{signal}语法。 - 布尔属性 由
Signal<bool>值驱动。 - 事件处理 支持闭包、
use_toggle、on_input_value、on_change_value、on_change_checked和NativeEventHandler。 - 事件委托 内置于渲染器中,以实现最佳性能。
- 组件嵌套 实现可组合、可复用的 UI 构建块。
这些高级特性使 euv 的 html! 宏成为使用 Rust 和 WASM 构建动态、交互式 Web 应用的强大工具。