组件系统
函数组件
函数组件接受类型化的 Props 结构体作为参数,html! 宏自动将属性映射到结构体字段:
use euv::*;
// 定义 Props 结构体(必须派生 Default)
#[derive(Default)]
struct MyCardProps {
title: &'static str,
children: VirtualNode,
}
// 组件函数接收 VirtualNode<Props>,通过 try_get_props() 提取类型化 Props
#[component]
pub fn my_card(node: VirtualNode<MyCardProps>) -> VirtualNode {
let MyCardProps { title, children, .. } = node.try_get_props().unwrap_or_default();
html! {
div {
class: c_card()
h3 {
class: c_card_title()
title
}
children
}
}
}提示
Props 结构体必须派生 Default,html! 宏使用 ..Default::default() 填充未提供的字段。因此所有字段都必须有合理的默认值。组件函数接收 VirtualNode<Props> 参数,通过 try_get_props() 提取类型化 Props,通过 try_get_child_node() 提取子节点。
定义 Props 结构体
Props 结构体需要满足以下要求:
- 派生
Default—html!宏使用..Default::default()填充未显式传递的字段 - 字段命名与
html!中属性名一致 — 属性名直接映射到结构体字段 children字段(可选) — 类型为VirtualNode,用于接收子节点
#[derive(Default)]
struct PrimaryButtonProps {
label: &'static str,
onclick: Option<Rc<dyn Fn(Event)>>,
disabled: bool,
children: VirtualNode,
}提示
事件回调属性类型为 Option<Rc<dyn Fn(Event)>>,字段名使用 on 前缀格式(如 onclick、on_change),在 html! 中用对应的属性名传递(如 onclick: handler)。
支持的字段类型
| 字段类型 | 说明 |
|---|---|
&'static str | 静态字符串值 |
String | 动态字符串值 |
bool | 布尔值,在 html! 中直接写属性名表示 true |
i32, f64 等 | 数值类型 |
Option<Rc<dyn Fn(Event)>> | 可选事件回调 |
Signal<T> | 响应式信号 |
VirtualNode | 子节点(通常命名为 children) |
Css | CSS 类引用 |
在 HTML 宏中使用组件
使用 #[component] 标记的函数会在 html! 宏中被识别为自定义组件,通过同名标签调用:
html! {
my_card {
title: "Card Title"
p { "Card content" }
}
}html! 宏自动将属性映射到 Props 结构体字段,上述代码等价于:
my_card(
MyCardProps {
title: "Card Title",
children: html! { p { "Card content" } },
..Default::default()
},
)提示
组件函数必须使用 #[component] 属性宏标记,html! 宏在编译时扫描项目源码查找所有 #[component] 标记的函数。未标记的函数名不会被识别为组件,而是作为原生 HTML 标签处理。
组件嵌套
html! {
my_card {
title: "Nested Components"
primary_button {
my_badge {
color: "#7c3aed"
text: "Badge"
}
}
}
}component 属性宏
使用 #[component] 标记函数组件,html! 宏在编译时扫描项目源码查找所有 #[component] 标记的函数,将其识别为自定义组件:
#[component]
pub fn my_card(node: VirtualNode<MyCardProps>) -> VirtualNode {
let MyCardProps { title, children, .. } = node.try_get_props().unwrap_or_default();
html! {
div { h3 { title } children }
}
}提示
#[component] 属性宏本身是透传的(不会修改函数体),但它是 html! 宏识别组件的必要标记。只有被 #[component] 标记的函数才会在 html! 宏中作为组件标签使用,未标记的标识符一律被视为原生 HTML 元素。
提取子节点
#[component] 函数接收 VirtualNode<Props> 参数,可通过 try_get_child_node() 提取子节点:
#[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
}
}
}try_get_child_node() 根据子节点数量返回不同变体:0 个子节点返回 Empty,1 个返回该子节点本身,多个返回 Fragment。
提示
也可以在 Props 结构体中定义 children: VirtualNode 字段,html! 宏会自动将子节点映射到该字段。两种方式等价,选择更直观的方式即可。