euv 宏系统深入
euv 的宏系统是其最强大的特性之一,提供了一种声明式的、符合人体工程学的语法,用于构建虚拟 DOM 树、管理组件生命周期以及编排响应式状态。euv 中的宏——包括 html!、#[component]、watch!、computed!、use_signal 和 Css::inject_css——共同创造了一种既自然又类型安全的开发体验。本文将全面介绍这些宏的工作原理、生成内容以及如何有效使用它们。
html! 宏
html! 宏是 euv API 的核心。它提供了一种类似 JSX 的语法,可以在 Rust 代码中直接构建虚拟 DOM 树。该宏在编译时将声明式标记转换为 VirtualNode 树,在代码运行之前捕获语法错误和类型不匹配。
基础元素
在最简单的情况下,html! 创建纯 HTML 元素:
html! {
div { "Hello, world!" }
}
这会创建一个标签为 "div" 的 VirtualNode::Element,以及一个包含 "Hello, world!" 的 VirtualNode::Text 子节点。
嵌套元素
该宏支持任意层级的嵌套,反映 HTML 的结构:
html! {
div {
h3 { "Title" }
p { "Content goes here." }
}
}
条件渲染
html! 宏支持 if/else 表达式进行条件渲染:
html! {
if is_visible {
p { "This is visible!" }
} else {
p { "This is hidden." }
}
}
条件在渲染时求值,只有活动分支会被包含在虚拟 DOM 树中。当条件发生变化时,渲染器对旧树和新树进行差异比较并应用最小变更。
match 表达式
对于更复杂的条件逻辑,html! 宏支持 match 表达式:
html! {
match page {
"home" => { h1 { "Welcome Home" } },
"about" => { h1 { "About Us" } },
_ => { h1 { "Page Not Found" } },
}
}
使用 for 循环渲染列表
html! 宏支持 for 循环来渲染动态列表:
html! {
for item in items {
li { item.name }
}
}
每次迭代生成一个 VirtualNode,被收集到父节点的子节点中。
响应式属性
html! 宏中的属性可以是响应式的。当信号值变化时,属性会自动更新:
let count: Signal<i32> = use_signal(|| 0);
html! {
div {
class: "counter"
"Count: " { count.get() }
}
}
事件处理程序
html! 宏支持内联事件处理程序注册:
html! {
button {
onclick: |e| { /* handle click */ }
"Click me"
}
}
支持的事件包括 onclick、oninput、onchange 等。这些通过 euv 的事件委托系统注册。
#[component] 宏
#[component] 属性宏将普通 Rust 函数转换为 euv 组件。它用必要的样板代码包装函数,用于属性提取、子节点处理和虚拟 DOM 集成:
#[derive(Clone, Default)]
struct MyCardProps {
title: &'static str,
onclick: Option<Rc<dyn Fn(Event)>>,
disabled: Signal<bool>,
}
#[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
}
}
}
#[component] 宏生成的代码:
- 接受一个带有类型化 props 的
VirtualNode。 - 通过
try_get_props()提取 props。 - 通过
try_get_child_node()提取子节点。 - 返回一个新的
VirtualNode,代表组件的输出。
watch! 宏
watch! 宏创建一个响应式观察器,当某个或多个信号变化时运行闭包:
let celsius: Signal<f64> = use_signal(|| 0.0);
let fahrenheit: Signal<f64> = use_signal(|| 32.0);
watch!(celsius, |c| {
fahrenheit.set(c * 9.0 / 5.0 + 32.0);
});
watch! 宏自动跟踪闭包内读取的信号,并在其中任何一个发生变化时重新运行闭包。这是 euv 中派生状态的基础。
关键特性:
- 自动依赖追踪:宏读取闭包内访问的信号,并为每个信号创建订阅。
- 自动清理:当组件被卸载时,观察器会自动移除。
- 多信号观察:你可以在单个
watch!调用中观察多个信号。
computed! 宏
computed! 宏创建一个派生信号,当其依赖项变化时自动更新。与运行副作用的 watch! 不同,computed! 产生一个新信号:
let count: Signal<i32> = use_signal(|| 0);
let doubled: Signal<i32> = computed!(count, |c| c * 2);
可以像读取普通信号一样读取计算信号,它始终反映最新的派生值。
信号创建:use_signal
use_signal 宏创建一个带有初始值的响应式信号:
let count: Signal<i32> = use_signal(|| 0);
信号也可以使用 Signal::create() 和 Signal::default() 创建:
let count: Signal<i32> = Signal::create(0);
let default_count: Signal<i32> = Signal::default();
信号支持 get() 读取和 set() 写入:
count.get(); // 读取当前值
count.set(42); // 设置新值
Css::inject_css 宏
euv 通过 Css::inject_css 函数提供 CSS 注入功能,允许你在运行时注入样式表:
Css::inject_css("body { margin: 0; padding: 0; }");
这对于动态样式、主题化和组件作用域 CSS 很有用。结合用于应用类的 class 宏,你可以完全控制样式。
宏如何协同工作
euv 宏系统的真正威力来自这些宏如何组合在一起。以下是一个协同使用多个宏的示例:
#[derive(Clone, Default)]
struct TemperatureProps {
celsius: Signal<f64>,
}
#[component]
pub fn temperature_display(node: VirtualNode<TemperatureProps>) -> VirtualNode {
let celsius = node.try_get_props().unwrap_or_default().celsius;
let fahrenheit: Signal<f64> = use_signal(|| 0.0);
watch!(celsius, |c| {
fahrenheit.set(c * 9.0 / 5.0 + 32.0);
});
html! {
div {
p { "Celsius: " { celsius.get() } }
p { "Fahrenheit: " { fahrenheit.get() } }
}
}
}
在这个例子中:
#[component]定义了一个可复用组件。use_signal创建了一个用于华氏度值的响应式信号。watch!保持华氏度值与摄氏度值同步。html!构建虚拟 DOM 输出。
宏展开与编译时安全性
euv 宏系统的关键优势之一是大部分工作在编译时完成。html! 宏是一个过程宏,它解析声明式语法并生成构建虚拟 DOM 树的 Rust 代码。这意味着:
- 语法错误 在编译时被捕获,而不是运行时。
- 类型不匹配(例如,在期望信号的地方传递字符串)被 Rust 编译器捕获。
- 性能 极佳,因为宏生成的是优化过的代码,没有运行时解析开销。
最佳实践
-
保持宏使用可读性强。 深度嵌套的
html!宏可能变得难以阅读。将复杂子树提取到单独的组件中。 -
对副作用使用
watch!。 当信号变化时需要运行代码(例如,更新另一个信号或触发操作)时,使用watch!。 -
对派生值使用
computed!。 如果你需要一个从其他信号派生的信号,使用computed!或手动创建信号配合watch!。 -
利用
#[component]创建可复用 UI。 任何出现多次的 UI 片段都应该被提取为#[component]。 -
对动态样式使用
Css::inject_css。 当你需要在运行时注入样式时(例如,用于主题化或组件作用域 CSS),使用Css::inject_css。
总结
euv 的宏系统是一套精心设计的工具,它们协同工作,提供声明式、类型安全和高效的开发体验。从构建虚拟 DOM 树的 html! 宏,到定义可复用组件的 #[component] 宏,再到管理响应式状态的 watch! 和 computed! 宏,每个宏都有特定用途,并能与其他宏干净地组合。通过理解这些宏的工作原理并遵循最佳实践,你可以自信且高效地构建复杂的 euv 应用程序。