html! 宏
基本用法
use euv::*;
html! {
div {
class: c_container()
h1 { "Hello, euv!" }
button {
onclick: move |_| { /* handle click */ }
"Click me"
}
}
}字符串属性
html! {
input {
r#type: "text"
placeholder: "Enter your name"
value: "default value"
}
}提示
Rust 关键字(如 type)需使用 r# 前缀:r#type: "text"
信号属性
let count: Signal<i32> = use_signal(|| 0);
html! {
span { count }
}事件属性
事件闭包签名为 FnMut(Event),其中 Event 是 web_sys::Event:
html! {
button {
onclick: move |event: Event| {
// 处理点击
}
"Click me"
}
}CSS 类属性
html! {
div {
class: c_container()
"Content"
}
}多 class 合并
同一元素上可以声明多个 class: 属性,框架会自动将它们合并为一个空格分隔的类名字符串:
html! {
div {
class: c_flex_row()
class: c_padding()
class: c_card()
"Multiple classes"
}
}提示
多个 class: 属性在编译时自动合并,效果等同于将所有类名拼接。如果其中任何一个 class 值是响应式 Signal,合并后的结果也会自动成为响应式属性。
多 style 合并
同一元素上可以声明多个 style: 属性,框架会自动将它们合并为完整的 CSS 样式字符串:
html! {
div {
style: {display: "flex"; gap: "10px";}
style: {padding: "20px"; background: "white";}
"Merged styles"
}
}提示
多个 style: 属性在编译时自动合并。如果其中任何一个 style 包含响应式 if 条件,合并后的结果也会自动成为响应式属性。
内联样式属性
支持两种语法——对象语法和表达式语法:
对象语法
html! {
div {
style: {display: "flex"; padding: "10px"; font_size: "14px";}
"Content"
}
}动态样式值
样式属性值支持花括号包裹的动态表达式:
html! {
div {
style: {background: {color};}
"Dynamic background"
}
}样式中的条件渲染
样式属性值同样支持 if 条件渲染,实现响应式的样式切换:
let is_active: Signal<bool> = use_signal(|| false);
html! {
div {
style: {
color: if { is_active.get() } { "#4f46e5".to_string() } else { "inherit".to_string() };
font_weight: if { is_active.get() } { "700".to_string() } else { "400".to_string() };
}
"Conditional styles"
}
}提示
样式属性值中的 if 条件会自动包装为响应式 Signal<String>,信号变化时样式自动更新。
提示
内联样式使用 snake_case,自动转换为 kebab-case(如 font_size → font-size)。
布尔属性
let agree: Signal<bool> = use_signal(|| true);
html! {
input {
r#type: "checkbox"
checked: agree
}
}自定义属性
html! {
div {
data_role: "container"
data_id: "12345"
aria_label: "Demo section"
"Custom attributes"
}
}提示
data_* 和 aria_* 属性自动转换为 data-* 和 aria-* 格式。使用 r# 前缀处理 Rust 保留字。
条件渲染
if/else
html! {
if {show.get()} {
div { "Visible" }
} else {
""
}
}else if 链
html! {
if {score.get() >= 90} {
div { "Excellent" }
} else if {score.get() >= 60} {
div { "Pass" }
} else {
div { "Fail" }
}
}match 表达式
html! {
match {route.get().as_str()} {
"/" => { page_home() }
"/about" => { page_about() }
_ => { page_not_found() }
}
}注意
if 的 else 分支不能省略,match 必须包含 _ 通配分支。
属性值条件渲染
if 条件渲染不仅可用于子节点位置,还可在属性值中使用,实现响应式的属性切换:
let is_active: Signal<bool> = use_signal(|| false);
html! {
div {
class: if { is_active.get() } { c_active() } else { c_inactive() }
"Content"
}
}样式中同样支持条件渲染:
html! {
div {
style: {
color: if { is_active.get() } { "#4f46e5".to_string() } else { "inherit".to_string() };
border_bottom: if { is_active.get() } { "2px solid #4f46e5".to_string() } else { "2px solid transparent".to_string() };
}
"Tab item"
}
}提示
属性值中的 if 条件会自动包装为响应式 Signal<String>,信号变化时属性值自动更新,无需手动订阅。
列表渲染(for 循环)
html! 宏支持 for 循环语法来渲染动态列表:
let items: Signal<Vec<String>> = use_signal(|| vec!["Rust".to_string(), "euv".to_string()]);
html! {
ul {
for item in {items.get()} {
li { item }
}
}
}带索引的循环:
html! {
ul {
for (index, item) in {items.get().iter().enumerate()} {
li { item }
}
}
}提示
for 循环中的迭代表达式需用花括号 {} 包裹。每次信号变化时,循环体会自动重新求值。
带 key 的列表渲染
为列表项添加 key 属性可以启用 Keyed Diffing,优化列表重排序时的 DOM 操作:
struct Item {
id: String,
name: String,
}
html! {
ul {
for item in {items.get()} {
li {
key: item.id
item.name
}
}
}
}提示
key 属性帮助框架识别哪些节点可以复用。当列表项的顺序变化时,Keyed Diffing 通过 key 匹配已有 DOM 节点,仅移动节点而不重建,提升性能。如果列表项没有 key,框架按位置逐一对比。
嵌入表达式
动态表达式(响应式)
使用花括号 {expr} 包裹的表达式会自动包装为 DynamicNode,信号变化时自动重新渲染:
html! {
div {
{format!("Count: {}", count.get())}
}
}静态表达式
裸标识符(无花括号)通过 IntoNode 进行静态一次性转换,不会响应信号变化:
html! {
div {
count
}
}组件标签
标签名包含下划线 _ 的元素会被识别为自定义组件,框架自动查找同名函数并传入 VirtualNode 作为 props:
html! {
my_card {
title: "Card Title"
p { "Card content" }
}
}等价于调用 my_card(VirtualNode) 函数,其中 VirtualNode 包含 title 属性和子节点。
pub fn my_card(props: VirtualNode) -> VirtualNode {
let title: String = props.try_get_prop("title").unwrap_or_default();
let children: Vec<VirtualNode> = props.get_children();
html! {
div {
h3 { title }
{VirtualNode::Fragment(children)}
}
}
}提示
组件名必须包含下划线(如 my_card、primary_button),框架通过下划线区分自定义组件与原生 HTML 标签。组件的属性和子节点通过 try_get_prop、try_get_event、try_get_callback、get_children 等方法提取。
宏内部适配器
html! 宏内部使用两个适配器类型来统一处理不同类型的属性值,减少宏展开的代码量:
EventAdapter
EventAdapter<T> 用于事件属性,将三种输入统一转换为 AttributeValue::Event:
FnMut(Event)闭包 → 通过NativeEventHandler包装NativeEventHandler直接传入 → 原样使用Option<NativeEventHandler>→ 有值时转换为Event,无值时转换为Text("")
AttrValueAdapter
AttrValueAdapter<T> 用于非事件属性,处理事件闭包和响应式值的分发:
- 事件属性(以
on开头)→ 包装为AttributeValue::Event - 非事件属性 → 通过
IntoReactiveValue转换为AttributeValue
提示
这两个适配器是宏内部实现细节,不需要手动使用。它们的作用是简化宏展开代码,避免为每个属性位置生成大量辅助类型。