虚拟 DOM
VirtualNode 变体
VirtualNode<T> 是虚拟 DOM 的核心枚举,泛型参数 T 携带组件 Props 类型(非组件节点默认 T = ()),支持以下变体:
| 变体 | 说明 | |
|---|---|---|
Element | HTML 元素节点,包含 tag(Tag 枚举)、attributes、children、key、props(仅组件有) | |
Text | 文本节点(TextNode),可能绑定响应式信号 | |
Fragment | 片段节点,包含多个子节点而无包装元素 | |
Dynamic | 动态节点(DynamicNode),信号变化时自动重新渲染 | |
Empty | 空占位节点(默认值) |
Tag 枚举
Tag 枚举标识元素节点的类型:
| 变体 | 说明 | |
|---|---|---|
Element(String) | 标准 HTML 元素或自定义 HTML5 元素(如 Web Components) | |
Component(String) | 用户定义的组件函数(通过 #[component] 标记) |
html! 宏自动判断标签类型:标识符标签如果对应 #[component] 标记的函数,生成 Tag::Component;否则生成 Tag::Element。字符串字面量标签始终生成 Tag::Element。
创建节点
use euv::*;
// 创建动态节点(信号变化时自动重新渲染)
let count: Signal<i32> = use_signal(|| 0);
let node: VirtualNode = VirtualNode::create_dynamic(move || {
html! {
div { "Count: " count }
}
});VirtualNode 构建方法
| 方法 | 说明 | |
|---|---|---|
create_dynamic<F>(render_fn: F) -> Self | 从渲染闭包创建动态节点 | |
create_dynamic_with_context<F>(render_fn: F) -> Self | 从带 HookContext 的渲染闭包创建动态节点 |
VirtualNode 提取方法
| 方法 | 返回值 | 说明 | |
|---|---|---|---|
try_get_tag_name(&self) | Option<String> | 获取标签名(Element 和 Component 节点返回标签名,其他返回 None) | |
try_get_children(&self) | Option<&Vec<Self>> | 获取子节点引用(Element 和 Fragment 返回 Some,其他返回 None) | |
has_children(&self) | bool | 判断是否有非空子节点 | |
try_get_props(&self) | Option<T> | 克隆组件 Props(仅 Element 节点有 Props 时返回 Some,要求 T: Clone) | |
try_get_child_node(&self) | VirtualNode | 获取子节点作为 VirtualNode(0 个子节点返回 Empty,1 个返回该子节点,多个返回 Fragment) |
使用示例
// 获取标签名
let tag_name: Option<String> = node.try_get_tag_name();
// 获取子节点引用
let children: Option<&Vec<VirtualNode>> = node.try_get_children();
// 判断是否有子节点
let has_children: bool = node.has_children();
// 获取组件 Props(类型化方式)
let props: Option<MyCardProps> = node.try_get_props();
// 获取子节点作为 VirtualNode(适用于 children 字段)
let child_node: VirtualNode = node.try_get_child_node();提示
try_get_child_node 在 #[component] 函数中常用于提取 children:let children: VirtualNode = node.try_get_child_node();。它会根据子节点数量自动返回合适的变体。
AttributeValue 枚举
| 变体 | 说明 | |
|---|---|---|
Text(String) | 静态字符串值 | |
Signal(Signal<String>) | 响应式信号值 | |
Event(NativeEventHandler) | 事件处理器 | |
Dynamic(String) | 动态表达式值(用于组件 props) | |
Css(Css) | CSS 类引用 |
IntoNode / AsReactiveText Trait
框架提供了两个核心转换 trait:
| Trait | 方法 | 说明 | |
|---|---|---|---|
IntoNode | into_node(self) -> VirtualNode | 消耗转换,String、&str、i32、usize、bool、Signal<T>、FnMut() -> VirtualNode 闭包、Vec<VirtualNode>、Option<VirtualNode>、Option<Vec<VirtualNode>>、VirtualNode 本身等实现 | |
AsReactiveText | as_reactive_text(&self) -> VirtualNode | Signal<T> 转响应式文本节点(Signal<T> 的 IntoNode 实现内部调用此方法),要求 T: Display |
提示
在 html! 宏中,裸标识符通过 IntoNode 转换(静态),{expr} 花括号表达式自动包装为 DynamicNode(响应式)。FnMut() -> VirtualNode 闭包的 IntoNode 实现会自动创建 DynamicNode 并附带 HookContext。
内联样式
// 从键值对数组直接创建 CSS 字符串(静态样式,html! 宏内部使用的优化方法)
let css_string: String = Css::create_style_string(&[
("flex_direction", "column"),
("padding", "10px"),
]);
// 从 owned 键值对数组创建 CSS 字符串(响应式样式,用于 if 条件场景)
let css_string: String = Css::create_style_string_owned(&[
("color".to_string(), "red".to_string()),
("font_size".to_string(), "16px".to_string()),
]);Css 样式方法
| 方法 | 说明 | |
|---|---|---|
Css::create_style_string(props: &[(&str, &str)]) -> String | 从借用键值对数组直接创建 CSS 字符串(html! 宏内部使用的优化方法) | |
Css::create_style_string_owned(props: &[(String, String)]) -> String | 从 owned 键值对数组创建 CSS 字符串(用于响应式 if 条件样式) |
提示
样式属性名使用 snake_case,框架自动转换为 kebab-case(如 flex_direction → flex-direction)。
CSS 类
// class! 宏定义的 CSS 类可直接在 html! 中使用
html! {
div {
class: c_card()
"Content"
}
}Css 结构体包含 name、style、pseudo_rules 和 media_rules 字段,首次使用时通过 inject_style() 自动将样式注入 DOM 的 <style> 元素。注入规则为:基础样式 → 伪类/伪元素规则 → 媒体查询规则。
Css 提供的主要方法:
| 方法 | 说明 | |
|---|---|---|
new(name, style, pseudo_rules, media_rules) -> Self | 创建 CSS 类(包含伪类规则和媒体查询) | |
inject_style(&self) | 将样式注入 DOM(重复调用只注入一次) | |
inject_css(css: &str) | 向全局 <style> 元素追加 CSS 文本 |
提示
CSS 类首次使用时自动注入样式到 DOM,无需手动引入 CSS 文件。Css 实现了 Display trait,format!("{}", css) 输出类名字符串,用于响应式 if 条件中的 class 属性。