虚拟 DOM
VirtualNode 变体
VirtualNode 是虚拟 DOM 的核心枚举,支持以下变体:
| 变体 | 说明 |
|---|---|
Element | HTML 元素节点,包含 tag、attributes、children、key |
Text | 文本节点,可能绑定响应式信号 |
Fragment | 片段节点,包含多个子节点而无包装元素 |
Dynamic | 动态节点,信号变化时自动重新渲染 |
Empty | 空占位节点(默认值) |
创建节点
use euv::*;
// 创建元素节点
let node: VirtualNode = VirtualNode::get_element_node("div");
// 创建文本节点
let text: VirtualNode = VirtualNode::get_text_node("Hello");
// 链式添加属性和子节点
let node: VirtualNode = VirtualNode::get_element_node("div")
.with_attribute("id", AttributeValue::Text("app".to_string()))
.with_child(VirtualNode::get_text_node("Content"));提取属性
// 提取字符串属性
let title: Option<String> = props.try_get_prop("title");
// 提取类型化属性(自动解析字符串为指定类型)
let max_count: Option<i32> = props.try_get_typed_prop("max_count");
let disabled: Option<bool> = props.try_get_typed_prop("disabled");
// 提取信号属性(返回原始 Signal<String>,可响应式订阅)
let value_signal: Option<Signal<String>> = props.try_get_signal_prop("value");
// 提取标准 HTML 属性
let placeholder: String = props.try_get_prop("placeholder").unwrap_or_default();
let value: String = props.try_get_prop("value").unwrap_or_default();
// 提取事件处理器
let onclick: Option<NativeEventHandler> = props.try_get_event("onclick");
// 通过自定义名称提取回调
let on_click: Option<NativeEventHandler> = props.try_get_callback("on_click");
// 提取子节点
let children: Vec<VirtualNode> = props.get_children();
// 提取文本内容
let text: Option<String> = node.try_get_text();
// 判断是否为组件节点
let is_component: bool = node.is_component();
// 获取标签名
let tag_name: Option<String> = node.tag_name();提示
try_get_signal_prop 返回原始 Signal<String>,适用于需要响应式订阅属性变化的场景,而非仅获取快照值。
提示
try_get_typed_prop 通过 FromStr 自动将属性字符串解析为目标类型,适合提取 i32、bool、f64 等类型值。
AttributeValue 枚举
| 变体 | 说明 |
|---|---|
Text(String) | 静态字符串值 |
Signal(Signal<String>) | 响应式信号值 |
Event(NativeEventHandler) | 事件处理器 |
Dynamic(String) | 动态表达式值(用于组件 props) |
Css(CssClass) | CSS 类引用 |
IntoNode / AsReactiveText Trait
框架提供了两个核心转换 trait:
| Trait | 方法 | 说明 |
|---|---|---|
IntoNode | into_node(self) -> VirtualNode | 消耗转换,String、&str、i32、usize、bool、Signal<T>、FnMut() -> VirtualNode 闭包等实现 |
AsReactiveText | as_reactive_text(&self) -> VirtualNode | Signal<T> 转响应式文本节点(Signal<T> 的 IntoNode 实现内部调用此方法) |
提示
在 html! 宏中,裸标识符通过 IntoNode 转换(静态),{expr} 花括号表达式自动包装为 DynamicNode(响应式)。FnMut() -> VirtualNode 闭包的 IntoNode 实现会自动创建 DynamicNode 并附带 HookContext。
属性值转换 Trait
框架提供了两个属性值转换 trait,用于 html! 宏中不同类型的属性值适配:
| Trait | 方法 | 说明 |
|---|---|---|
IntoReactiveValue | into_reactive_value(self) -> AttributeValue | 将值转换为 AttributeValue,String、&str、Signal<String>、Signal<bool>、CssClass、&'static CssClass、bool、i32、f64 等实现 |
IntoReactiveString | into_reactive_string(self) -> String | 将值转换为字符串,用于属性值中的 if 条件响应式求值,String、&str、CssClass、bool、i32、u32、f64、Signal<String>、Signal<bool> 等实现 |
提示
IntoReactiveValue 在 html! 宏中自动调用,将各种类型的属性值(字符串、信号、CSS 类、布尔值等)统一转换为 AttributeValue。bool 和 i32/f64 通过 IntoReactiveValue 转换为 AttributeValue::Dynamic,保留原始类型信息,使组件可以通过 try_get_typed_prop 提取。通常不需要手动使用这些 trait。
动态节点创建
框架提供了两个函数用于手动创建 VirtualNode::Dynamic:
create_dynamic_node
从渲染闭包创建动态节点,信号变化时自动重新渲染:
let count: Signal<i32> = use_signal(|| 0);
let count_for_closure: Signal<i32> = count;
let node: VirtualNode = create_dynamic_node(move || {
html! {
div { "Count: " count_for_closure }
}
});create_dynamic_node_with_context
从接收 &mut HookContext 的渲染闭包创建动态节点,用于 match 表达式中需要 Hook 上下文隔离的场景。框架内部在处理 match 分支时自动使用此函数:
提示
通常不需要手动使用这两个函数,html! 宏会自动为 if、match、for 和花括号表达式创建动态节点。仅在需要极度自定义的动态渲染逻辑时才使用。
DynamicNode 方法
DynamicNode 提供了以下方法:
| 方法 | 说明 | |
|---|---|---|
render(&self) -> VirtualNode | 调用渲染闭包并返回生成的虚拟节点 |
提示
DynamicNode::render() 内部会先重置 Hook 索引(reset_hook_index()),然后调用渲染闭包。框架在 setup_dynamic_node 时自动处理渲染和重新渲染逻辑,通常不需要手动调用 render()。
TextNode 信号访问
TextNode 除了包含文本内容(content)外,还可以绑定一个可选的响应式信号(signal),用于自动更新文本:
// TextNode 包含 content 和 signal 两个内部字段
// signal 字段为 Option<Signal<String>>,在框架内部使用提示
TextNode 的信号绑定由框架在创建响应式文本节点时自动设置,通常不需要手动操作。
内联样式
let style: Style = Style::default()
.property("flex_direction", "column")
.property("padding", "10px");
let css_string: String = style.to_css_string();
// 从键值对数组直接创建 CSS 字符串(更高效,无需中间对象)
let css_string: String = Style::create_style_string(&[
("flex_direction", "column"),
("padding", "10px"),
]);提示
样式属性名使用 snake_case,框架自动转换为 kebab-case(如 flex_direction → flex-direction)。
提示
Style::create_style_string 是 html! 宏内部使用的优化方法,直接从键值对数组生成 CSS 字符串,跳过 Style 和 Vec<StyleProperty> 的中间分配。手动构建样式时,链式 property() + to_css_string() 更直观。
响应式属性辅助函数
框架提供了两个辅助函数,用于创建响应式属性值(html! 宏内部自动使用,通常不需要手动调用):
create_reactive_style_attribute
创建响应式样式属性值,闭包在信号变化时自动重新求值:
let is_active: Signal<bool> = use_signal(|| false);
let style_attr: AttributeValue = create_reactive_style_attribute(|| {
if is_active.get() { "color: red;".to_string() } else { "color: gray;".to_string() }
});create_reactive_attr_signal
创建响应式属性信号值,用于属性值中包含 if 条件的场景:
let visible: Signal<bool> = use_signal(|| true);
let attr_value: AttributeValue = create_reactive_attr_signal(|| {
if visible.get() { "show".to_string() } else { "hide".to_string() }
});提示
这两个函数在 html! 宏内部自动调用,将属性值中的 if 条件和动态样式包装为响应式 Signal<String>。手动创建时通常用于构建自定义属性适配器。
CSS 类
// class! 宏定义的 CSS 类可直接在 html! 中使用
html! {
div {
class: c_card()
"Content"
}
}CssClass 结构体包含 name、style、pseudo_rules 和 media_rules 字段,首次使用时通过 inject_style() 自动将样式注入 DOM 的 <style> 元素。注入规则为:基础样式 → 伪类/伪元素规则 → 媒体查询规则。
CssClass 提供的主要方法:
| 方法 | 说明 | |
|---|---|---|
new(name, style) -> Self | 创建 CSS 类并自动注入样式 | |
new_with_rules(name, style, pseudo_rules, media_rules) -> Self | 创建带伪类/伪元素和媒体查询的 CSS 类 | |
inject_style(&self) | 将样式注入 DOM(重复调用只注入一次) | |
inject_css(css: &str) | 向全局 <style> 元素追加 CSS 文本 | |
parse_pseudo_rules(input: &str) -> Vec<PseudoRule> | 解析序列化的伪类规则字符串 | |
parse_media_rules(input: &str) -> Vec<MediaRule> | 解析序列化的媒体查询规则字符串 |
提示
CSS 类首次使用时自动注入样式到 DOM,无需手动引入 CSS 文件。