html-macro-advanced-features[20260803210802]

0 阅读1分钟

HTML 宏高级特性

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

html! 宏是 euv 模板系统的核心。虽然其用于创建元素和文本节点的基本语法很直观,但 euv 提供了几个高级特性来实现复杂、动态的用户界面:条件渲染、响应式属性值、内联样式、布尔属性和事件委托。本文将详细探讨每个特性。

快速回顾:基本 html! 语法

html! 宏使用类似 Rust 的语法来描述虚拟 DOM 节点:

html! {
    div {
        h1 { "Hello, World!" }
        p { "This is a paragraph." }
    }
}

这会创建一个虚拟 DOM 树,euv 的渲染器会将其高效地映射到真实 DOM 元素。宏支持嵌套、文本内容和子组件。

使用 if/else 进行条件渲染

euv 的 html! 宏通过 if/else 块支持条件渲染。关键规则:else 分支不能省略。如果需要一个空分支,使用空字符串 ""

基本 if/else

html! {
    if is_logged_in {
        div { "Welcome back!" }
    } else {
        div { "Please log in." }
    }
}

空分支

当你需要一个不渲染任何内容的分支时:

html! {
    if show_banner {
        div { "Special offer!" }
    } else {
        ""
    }
}

""(空字符串)会渲染为 Empty 虚拟节点变体,不产生任何 DOM 输出。

链式条件

你可以链式连接多个条件:

html! {
    if state == 0 {
        div { "Idle" }
    } else if state == 1 {
        div { "Loading..." }
    } else if state == 2 {
        div { "Complete" }
    } else {
        div { "Unknown state" }
    }
}

使用 match 进行条件渲染

对于更复杂的分支逻辑,euv 在 html! 中提供了 match。关键规则:match 必须始终包含一个 _ 通配分支

基本 match

html! {
    match page {
        "home" => { div { "Home Page" } },
        "about" => { div { "About Page" } },
        "contact" => { div { "Contact Page" } },
        _ => { div { "404 Not Found" } }
    }
}

_ 通配分支是强制性的。它捕获所有未显式处理的情况。没有它,代码将无法编译。

复杂模式匹配

html! {
    match user_role {
        "admin" => { div { "Admin Dashboard" } },
        "editor" => { div { "Editor Panel" } },
        "viewer" => { div { "View Only" } },
        _ => { div { "Unauthorized" } }
    }
}

使用 for 循环渲染列表

html! 宏支持 for 循环来渲染列表。关键规则:迭代表达式必须用花括号 {} 包裹

基本 for 循环

html! {
    for item in {items} {
        li { item.name }
    }
}

{items} 语法告诉 euv items 是一个响应式表达式。当 items 信号变化时,列表会自动重新渲染。

渲染购物列表

let cart_items: Signal<Vec<String>> = use_signal(|| vec!["Apple", "Banana", "Cherry"]);

html! {
    ul {
        for item in {cart_items} {
            li { item }
        }
    }
}

嵌套循环

html! {
    for category in {categories} {
        div {
            h3 { category.name }
            for product in {category.products} {
                div { product.name }
            }
        }
    }
}

响应式属性值

html! 宏支持响应式属性值。当信号值变化时,对应的 DOM 属性会自动更新。

基本响应式属性

let is_disabled: Signal<bool> = use_signal(|| false);

html! {
    button {
        disabled: is_disabled
        "Submit"
    }
}

动态类名

let active_tab: Signal<String> = use_signal(|| "home".to_string());

html! {
    div {
        class: active_tab
        "Tab Content"
    }
}

样式属性

你可以响应式地设置样式属性:

let bg_color: Signal<String> = use_signal(|| "blue".to_string());

html! {
    div {
        style: "background-color: {bg_color}"
        "Colored Box"
    }
}

内联样式

除了响应式属性,euv 在 html! 宏中直接支持内联样式。你可以混合使用静态和动态样式值:

let font_size: Signal<String> = use_signal(|| "14px".to_string());

html! {
    div {
        style: "padding: 10px; font-size: {font_size}; border: 1px solid #ccc"
        "Styled content"
    }
}

静态值直接书写,而响应式值使用 {signal_name} 语法。

布尔属性

HTML 布尔属性如 disabledcheckedreadonlyrequired 在 euv 中自然处理:

let is_disabled: Signal<bool> = use_signal(|| false);
let is_checked: Signal<bool> = use_signal(|| true);

html! {
    div {
        input {
            type: "checkbox"
            checked: is_checked
        }
        button {
            disabled: is_disabled
            "Submit"
        }
    }
}

当信号值为 true 时,属性存在于 DOM 中。当为 false 时,它被移除。

事件处理

euv 在 html! 中提供了全面的事件处理系统。有几种不同的模式适用于不同的用例。

基于闭包的处理器

最灵活的方法是使用闭包:

button {
    onclick: move |event: Event| {
        count.set(count.get() + 1);
    }
    "Click"
}

use_toggle 辅助函数

对于简单的切换行为:

let is_active: Signal<bool> = use_signal(|| false);

button {
    onclick: use_toggle(is_active)
    "Toggle"
}

输入值辅助函数

euv 为表单输入提供了专门的辅助函数:

let input_text: Signal<String> = use_signal(|| "".to_string());
let checkbox_checked: Signal<bool> = use_signal(|| false);

html! {
    div {
        input {
            oninput: on_input_value(input_text)
        }
        input {
            onchange: on_change_value(input_text)
        }
        input {
            type: "checkbox"
            onchange: on_change_checked(checkbox_checked)
        }
    }
}
  • on_input_value(signal) — 每次按键时更新信号。
  • on_change_value(signal) — 在失焦或回车时更新信号。
  • on_change_checked(signal) — 在复选框切换时更新布尔信号。

NativeEventHandler

对于更精细的控制,可以使用 NativeEventHandler::create

NativeEventHandler::create("click", move |_event: Event| {
    // 处理事件
})

这让你可以直接访问原始事件对象,适用于自定义事件委托等高级场景。

事件委托

euv 的渲染器在底层使用事件委托。euv 不在每个元素上附加单独的事件监听器,而是在根级别附加单个监听器,根据虚拟 DOM 树来分发事件。这具有重要的性能优势:

  • 内存效率:更少的事件监听器意味着更少的内存使用。
  • 动态内容:通过 for 循环添加的新元素自动继承事件处理,无需重新附加监听器。
  • 一致性:所有元素的事件行为统一。

你不需要做任何特殊操作来从事件委托中受益——它在 euv 的渲染引擎中默认启用。

嵌套组件

html! 宏支持在组件内部嵌套其他组件:

#[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
        }
    }
}

使用方式:

html! {
    my_card {
        title: "My Card"
        "This is the card content."
    }
}

组件通过 try_get_props() 方法接收属性,通过 try_get_child_node() 接收子节点。#[component] 属性标记一个函数为组件工厂。

实用示例

示例 1:动态导航栏

let current_page: Signal<String> = use_signal(|| "home".to_string());

html! {
    nav {
        for page in {pages} {
            a {
                class: if page.id == current_page { "active" } else { "" }
                onclick: move |_| { current_page.set(page.id.clone()) }
                page.label
            }
        }
    }
}

示例 2:条件表单字段

let form_type: Signal<String> = use_signal(|| "personal".to_string());

html! {
    form {
        input { type: "text"; placeholder: "Name" }

        match form_type.as_str() {
            "personal" => {
                input { type: "email"; placeholder: "Personal Email" }
            },
            "business" => {
                div {
                    input { type: "email"; placeholder: "Business Email" }
                    input { type: "text"; placeholder: "Company Name" }
                }
            },
            _ => { "" }
        }

        button { type: "submit" "Submit" }
    }
}

示例 3:响应式数据表格

let rows: Signal<Vec<RowData>> = use_signal(|| vec![]);
let sort_column: Signal<String> = use_signal(|| "name".to_string());

html! {
    table {
        thead {
            tr {
                for col in {columns} {
                    th {
                        onclick: move |_| { sort_column.set(col.key.clone()) }
                        col.label
                    }
                }
            }
        }
        tbody {
            for row in {rows} {
                tr {
                    for col in {columns} {
                        td { row.get_field(col.key) }
                    }
                }
            }
        }
    }
}

总结

  • if/else 实现条件渲染。else 分支是强制性的;使用 "" 表示空分支。
  • match 提供基于模式的条件渲染。必须有 _ 通配分支。
  • for 循环渲染列表。迭代表达式必须用 {} 包裹。
  • 响应式属性 在信号变化时自动更新 DOM。
  • 内联样式 支持静态和动态值,使用 {signal} 语法。
  • 布尔属性Signal<bool> 值驱动。
  • 事件处理 支持闭包、use_toggleon_input_valueon_change_valueon_change_checkedNativeEventHandler
  • 事件委托 内置于渲染器中,以实现最佳性能。
  • 组件嵌套 实现可组合、可复用的 UI 构建块。

这些高级特性使 euv 的 html! 宏成为使用 Rust 和 WASM 构建动态、交互式 Web 应用的强大工具。


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