一款声明式 Rust WebAssembly UI 框架 - Euv(哎呦喂)

项目概述

Euv 是一个基于 Rust 语言开发的声明式、跨平台 UI 框架,构建在 WebAssembly 之上,融合了虚拟 DOM、响应式 Signal 系统以及类 HTML 宏语法,为开发者提供现代、高效且符合人体工程学的前端开发体验。

核心特性

  • 声明式 UI:通过 html! 宏编写类 JSX 语法,编译期类型检查,零运行时开销
  • 响应式 Signaluse_signal Hook 驱动的细粒度响应式系统,数据变更自动更新 DOM
  • 高性能虚拟 DOM:Keyed Diff 算法 + 增量 Patch,最小化 DOM 操作
  • 全局事件委托:基于事件冒泡的统一委托机制,减少事件监听器数量,降低内存占用
  • CSS-in-Rustclass! 宏 + css_vars! 宏,类型安全的样式定义,支持伪类、媒体查询
  • 组件系统#[component] 属性宏标注,Props 自动提取,组件组合零摩擦
  • CLI 工具链:内置 euv-cli,支持开发服务器、热更新、一键构建发布

架构设计亮点

Monorepo 架构

Crate 职责 说明
euv-core 核心运行时 虚拟 DOM、响应式系统、渲染器、事件系统
euv-macros 过程宏 html!class!css_vars!watch!var!#[component]
euv-cli CLI 工具 dev/build/fmt 三种模式,热更新、wasm-pack 集成
euv 统一入口 聚合 core + macros,对外提供完整 API

响应式系统架构

Signal (use_signal)
   │
   ├── .get() → 读取当前值
   ├── .set() → 更新值 + 通知监听器 + 调度 DOM 更新
   ├── .subscribe() / .replace_subscribe() → 注册变更回调
   │
   └── schedule_signal_update()
         │
         └── queueMicrotask → dispatch EuvSignalUpdate Event
               │
               └── DynamicNode 重新渲染 → 增量 Patch DOM

核心优化

  1. Microtask 批处理:多次 signal.set() 在同一微任务中自动合并,仅触发一次 DOM 更新
  2. batch_updates API:手动批量更新,抑制中间调度,适合复杂状态变更场景
  3. set_silent 静默更新:仅触发监听器但不调度 DOM 更新,用于属性信号内部同步
  4. skip_equal 优化DynamicNode 重新渲染前进行 visual_eq 比较,无视觉变化则跳过 Patch

虚拟 DOM Diff 算法

算法 适用场景 复杂度
Keyed Diff 所有子节点均带 key 属性 O(N) 建映射 + 最小 DOM 移动
Positional Diff 无 key 或部分子节点缺 key O(min(M, N)) 逐位比较

Keyed Diff 特性

  • 构建 old_key → old_index 映射表
  • 复用已有 DOM 节点,仅 Patch 属性差异
  • 通过 insert_before 最小化 DOM 移动操作
  • 反向遍历移除废弃节点,避免索引偏移

全局事件委托机制

DOM Element
   │
   └── data-euv-id="42" (自动分配)
         │
         └── Event Bubble → window 委托监听器
               │
               └── HandlerRegistry[(42, "click")] → HandlerSlot → 用户回调
  • 支持鼠标、键盘、焦点、表单、拖拽、触摸、滚轮、剪贴板、动画、过渡等 60+ 种事件类型
  • 每种事件类型仅注册一个全局监听器,通过 data-euv-id 精确路由
  • 组件卸载时自动清理事件处理器,杜绝内存泄漏

开发便捷性

1. 声明式 UI 编写

#[component]
fn primary_button(props: VirtualNode) -> VirtualNode {
    let PrimaryButtonProps { label, onclick, disabled } = props.into();
    html! {
        button {
            class: if { disabled } { c_primary_button_disabled() } else { c_primary_button() }
            onclick: onclick
            label
        }
    }
}

2. 响应式状态管理

fn page_conditional() -> VirtualNode {
    let show_details: Signal<bool> = use_signal(|| false);
    let user_type: Signal<String> = use_signal(|| "guest".to_string());
    html! {
        div {
            class: c_page_container()
            primary_button {
                label: "Toggle"
                onclick: use_toggle(show_details)
                "Toggle"
            }
            if { show_details.get() } {
                div { "Hidden Details" }
            } else {
                ""
            }
            match { user_type.get().as_str() } {
                "guest" => { div { "Welcome, guest!" } }
                "user"  => { div { "Hello, user!" } }
                _       => { div { "Welcome, admin!" } }
            }
        }
    }
}

3. CSS-in-Rust 样式系统

class! {
    pub c_card {
        background: var!(bg-card);
        border-radius: "12px";
        padding: "24px";
        box-shadow: var!(shadow-card);
        media("(max-width: 767px)") {
            padding: "16px";
            border-radius: "10px";
        }
    }

    pub c_nav_item_inactive {
        color: var!(text-nav-item);
        hover {
            background: "rgba(79, 70, 229, 0.04)";
            color: "#4f46e5";
        }
    }
}

css_vars! {
    pub c_theme_light {
        bg-primary: "#f8f9fb";
        text-primary: "#1a1a2e";
    }
}

4. 响应式副作用

let count = use_signal(|| 0_i32);
let name = use_signal(|| String::from("euv"));

watch!(count, name, |count_val, name_val| {
    web_sys::console::log_1(
        &format!("count={}, name={}", count_val, name_val).into()
    );
});

5. CLI 开发工具链

# 开发模式(热更新 + Dev Server)
euv-cli run --crate-path ./example

# 构建发布
euv-cli build --crate-path ./example --release

# 格式化宏代码
euv-cli fmt --path ./src

框架优势

1. 零成本抽象设计

  • 编译期宏展开html!class! 等宏在编译时生成 Rust 代码,运行时零解析开销
  • 类型安全:Props 自动提取、事件类型强校验,编译时消除 UI 逻辑错误
  • Signal Copy 语义Signal<T> 实现 Copy trait,仅复制指针地址,Rc 引用由全局注册表持有,无 GC 压力

2. 精细 DOM 更新策略

  • 属性级 Patch:新旧属性 HashMap 对比,仅更新变化的属性
  • 信号绑定属性AttributeValue::Signal 自动订阅变更,DOM 节点移除时自动清理监听器
  • DynamicNode 独立渲染:每个动态节点拥有独立的 RendererHookContext,实现局部更新
  • arm 切换检测match 分支切换时自动执行 render_full_replace,避免错误复用

3. 完善的生命周期管理

  • DOM 子树清理cleanup_dom_subtree 递归清理事件处理器、动态节点监听器、信号监听器
  • Hook 清理match 分支切换时执行 cleanups 队列,防止信号泄漏
  • Signal 存活标记alive 标志位确保已清理的 Signal 不再触发无效更新

4. 跨平台响应式布局

  • use_resize Hook:自动检测视口尺寸,防抖 150ms,桌面/移动端布局自适应切换
  • CSS 媒体查询class! 宏原生支持 media() 语法
  • 主题系统css_vars! 实现明暗主题,一行代码切换

技术栈对比

特性 Euv Yew Dioxus Leptos
虚拟 DOM ✅ Keyed + Positional Diff ❌ (细粒度)
响应式系统 ✅ Signal + Microtask 批处理 ❌ (手动更新)
类 JSX 宏 html! html! rsx! view!
CSS-in-Rust class! + css_vars!
全局事件委托 ✅ 60+ 事件类型
CLI 工具链 ✅ dev/build/fmt + 热更新
组件系统 #[component]
伪类/媒体查询 ✅ 原生支持
WASM 目标
Rust Edition 2024 2021 2021 2021

生态系统与社区

文档与学习资源

开源协议

  • 许可证:MIT License

适用场景

推荐使用场景

  1. Web 应用前端:需要高性能、类型安全的现代 Web 应用
  2. 内部管理系统:表单密集、条件渲染复杂的管理后台
  3. 交互式仪表盘:实时数据更新、响应式布局的数据看板
  4. 组件库开发:类型安全的 Props、CSS-in-Rust 样式隔离
  5. WASM 原生应用:追求极致性能的 WebAssembly 应用

核心能力

  • 60+ 事件类型覆盖:鼠标、键盘、焦点、表单、拖拽、触摸、剪贴板、动画等
  • 内置组件:Button、Card、Modal、Select、FormInput、Textarea、Badge、PageHeader、VConsole
  • 示例页面:Signals、Event、List、Conditional、Modal、Select、Async、Form、FileUpload、Timer、Animation、Browser、Lifecycle、Keep-Alive、ComponentBinding、CustomAttrs

总结

Euv 作为一款新兴的 Rust 声明式 UI 框架,在 WebAssembly 生态中独树一帜。通过创新的架构设计——Signal + Microtask 批处理响应式系统Keyed Diff 虚拟 DOM全局事件委托机制CSS-in-Rust 样式系统——为 Rust 前端开发提供了完整且高效的解决方案。

框架的核心优势在于:

  • 开发体验html! 宏 + class! 宏 + use_signal Hook,三位一体的声明式开发范式
  • 运行时性能:Microtask 批处理 + Keyed Diff + 事件委托,多层级优化确保极致渲染效率
  • 类型安全:编译期宏展开、Props 自动提取、事件类型强校验,将 UI 错误扼杀在编译阶段
  • 工程化支持:CLI 工具链提供 dev/build/fmt 三种模式,内置热更新和代码格式化

对于追求类型安全、高性能和开发效率的 Rust WebAssembly 项目,Euv 是一个值得考虑的优秀选择。随着 Rust 前端生态的不断发展,Euv 有望成为 WebAssembly UI 开发的重要工具。


更多推荐