Macro-System-Deep-Dive[20261004120312]

0 阅读1分钟

euv 宏系统深入

项目代码:github.com/euv-dev/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] 宏生成的代码:

  1. 接受一个带有类型化 props 的 VirtualNode。
  2. 通过 try_get_props() 提取 props。
  3. 通过 try_get_child_node() 提取子节点。
  4. 返回一个新的 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 编译器捕获。
  • 性能 极佳,因为宏生成的是优化过的代码,没有运行时解析开销。

最佳实践

  1. 保持宏使用可读性强。 深度嵌套的 html! 宏可能变得难以阅读。将复杂子树提取到单独的组件中。

  2. 对副作用使用 watch!。 当信号变化时需要运行代码(例如,更新另一个信号或触发操作)时,使用 watch!。

  3. 对派生值使用 computed!。 如果你需要一个从其他信号派生的信号,使用 computed! 或手动创建信号配合 watch!。

  4. 利用 #[component] 创建可复用 UI。 任何出现多次的 UI 片段都应该被提取为 #[component]。

  5. 对动态样式使用 Css::inject_css。 当你需要在运行时注入样式时(例如,用于主题化或组件作用域 CSS),使用 Css::inject_css。

总结

euv 的宏系统是一套精心设计的工具,它们协同工作,提供声明式、类型安全和高效的开发体验。从构建虚拟 DOM 树的 html! 宏,到定义可复用组件的 #[component] 宏,再到管理响应式状态的 watch! 和 computed! 宏,每个宏都有特定用途,并能与其他宏干净地组合。通过理解这些宏的工作原理并遵循最佳实践,你可以自信且高效地构建复杂的 euv 应用程序。


项目代码:github.com/euv-dev/euv