组件系统
2026/5/21大约 2 分钟euvuirustwasmusage-introductioncomponent
函数组件
函数组件接受 VirtualNode 作为 props,从中提取属性和子节点:
use euv::*;
pub fn my_card(props: VirtualNode) -> VirtualNode {
let children: Vec<VirtualNode> = props.get_children();
let MyCardProps { title, .. }: MyCardProps = props.into();
let children_node: VirtualNode = VirtualNode::Fragment(children);
html! {
div {
class: c_card()
h3 {
class: c_card_title()
title
}
children_node
}
}
}提取 Props
方式一:通过 From<VirtualNode> 提取(推荐)
定义一个 From<VirtualNode> 的结构体,实现类型安全的 props 提取:
struct MyCardProps {
title: String,
}
impl From<VirtualNode> for MyCardProps {
fn from(props: VirtualNode) -> Self {
MyCardProps {
title: props.try_get_prop("title").unwrap_or_default(),
}
}
}
// 在组件中使用
let MyCardProps { title, .. }: MyCardProps = props.into();提示
对于需要提取非 String 类型(如 bool、i32)的 props,使用 try_get_typed_prop 进行类型解析:
let disabled: bool = props.try_get_typed_prop("disabled").unwrap_or(false);
let max_count: i32 = props.try_get_typed_prop("max_count").unwrap_or(10);方式二:手动提取
// 提取字符串属性
let label: String = props.try_get_prop("label").unwrap_or_else(|| "Button".to_string());
// 提取标准 HTML 属性
let placeholder: String = props.try_get_prop("placeholder").unwrap_or_default();
let value: String = props.try_get_prop("value").unwrap_or_default();
let title: String = props.try_get_prop("title").unwrap_or_default();
// 提取信号属性(返回 Signal<String>,可响应式订阅)
let value_signal: Option<Signal<String>> = props.try_get_signal_prop("value");
// 提取事件处理器
let onclick: Option<NativeEventHandler> = props.try_get_event("onclick");
// 提取回调属性(自定义名称)
let on_click: Option<NativeEventHandler> = props.try_get_callback("on_click");
// 提取回调属性(自定义名称)
let on_click: Option<NativeEventHandler> = props.try_get_callback("on_click");
// 提取子节点
let children: Vec<VirtualNode> = props.get_children();提示
try_get_signal_prop 与 try_get_prop 的区别:前者返回原始 Signal<String>,适合需要响应式订阅属性变化的场景;后者返回 String 快照值。
在 HTML 宏中使用组件
自定义标签名(包含下划线 _)会自动匹配同名的函数组件:
html! {
my_card {
title: "Card Title"
p { "Card content" }
}
}提示
组件名必须包含下划线(如 my_card、primary_button),框架通过下划线判断是自定义组件还是原生 HTML 标签。
组件嵌套
html! {
my_card {
title: "Nested Components"
primary_button {
my_badge {
color: "#7c3aed"
text: "Badge"
}
}
}
}component 属性宏
使用 #[component] 标记函数组件:
#[component]
pub fn my_card(props: VirtualNode) -> VirtualNode {
let title: String = props.try_get_prop("title").unwrap_or_default();
html! {
div { h3 { title } }
}
}提示
目前 #[component] 是透传宏,不会修改函数体。它的作用是标记函数作为组件的意图,为未来的编译时检查做准备。