Rust前端开发框架Dioxus的使用,dioxus-lib提供跨平台UI构建与高效渲染方案

Rust前端开发框架Dioxus的使用,dioxus-lib提供跨平台UI构建与高效渲染方案

Dioxus是一个用Rust构建的跨平台用户界面框架,可以快速开发复杂的用户界面。任何Dioxus应用都可以在Web浏览器、桌面应用、移动应用中运行。

主要特点

  • 受React启发,支持类似的概念:

    • 状态管理钩子
    • VirtualDom和差异比较
    • 并发、纤程和异步渲染
    • JSX风格的模板语法
  • 性能优越:比许多其他Rust UI库(Yew/Percy)性能更好,比React性能更优

基本使用

简单示例

use dioxus::prelude::*;

fn main() {
    dioxus::launch(App);
}

#[component]
fn App() -> Element {
    rsx! { "hello world!" }
}

元素和组件

let value = "123";

rsx! {
    div {
        class: "my-class {value}",                  // 属性
        onclick: move |_| info!("clicked!"),   // 监听器
        h1 { "hello world" },                       // 子元素
    }
}

循环和条件

rsx! {
    div {
        for _ in 0..10 {
            span { "hello world" }
        }
    }
}

带属性的组件

#[derive(Props, PartialEq)]
struct HeaderProps {
    title: String,
    color: String,
}

#[component]
fn Header(props: HeaderProps) -> Element {
    rsx! {
        div {
            background_color: "{props.color}"
            h1 { "{props.title}" }
        }
    }
}

状态管理

#[component]
fn App() -> Element {
    let mut count = use_signal(|| 0);

    rsx!(
        div { "Count: {count}" }
        button { onclick: move |_| count += 1, "Increment" }
        button { onclick: move |_| count -= 1, "Decrement" }
    )
}

完整示例

这是一个完整的计数器应用示例:

use dioxus::prelude::*;

fn main() {
    dioxus::launch(App);
}

#[component]
fn App() -> Element {
    let mut count = use_signal(|| 0);

    rsx! {
        div {
            h1 { "计数器示例" }
            
            div {
                class: "counter-display",
                "当前值: {count}"
            }
            
            div {
                class: "button-group",
                
                button {
                    class: "increment-btn",
                    onclick: move |_| count += 1,
                    "增加"
                }
                
                button {
                    class: "decrement-btn",
                    onclick: move |_| count -= 1,
                    "减少"
                }
                
                button {
                    class: "reset-btn",
                    onclick: move |_| count.set(0),
                    "重置"
                }
            }
        }
    }
}

跨平台支持

Dioxus支持构建:

  • Web应用
  • 桌面应用
  • 移动应用
  • 服务器端渲染
  • 预渲染和重新hydration

安装

在Cargo.toml中添加依赖:

dioxus-lib = "0.6.2"

或者运行命令:

cargo add dioxus-lib

Dioxus提供了高效、灵活的方式来构建跨平台用户界面,结合了Rust的性能优势与现代UI开发的便利性。


1 回复

Rust前端开发框架Dioxus使用指南

Dioxus简介

Dioxus是一个基于Rust的声明式UI框架,用于构建跨平台用户界面。它受到React的启发,提供了类似的组件化开发体验,同时利用Rust的性能优势。

Dioxus-lib是该框架的核心库,支持:

  • Web平台(编译为WebAssembly)
  • 桌面应用(通过Tauri或Wry)
  • 移动应用(实验性支持)
  • 服务端渲染

安装与设置

首先在Cargo.toml中添加依赖:

[dependencies]
dioxus = "0.4"  # 请检查最新版本
dioxus-web = "0.4"  # 用于Web目标

基本使用

创建简单组件

use dioxus::prelude::*;

fn App(cx: Scope) -> Element {
    cx.render(rsx! {
        div {
            h1 { "欢迎使用Dioxus!" }
            Counter {}
        }
    })
}

fn Counter(cx: Scope) -> Element {
    let mut count = use_state(cx, || 0);
    
    cx.render(rsx! {
        div {
            p { "当前计数: {count}" }
            button { onclick: move |_| count += 1, "增加" }
            button { onclick: move |_| count -= 1, "减少" }
        }
    })
}

启动应用

对于Web目标:

fn main() {
    dioxus_web::launch(App);
}

对于桌面应用(需要额外配置):

fn main() {
    dioxus_desktop::launch_cfg(App, |c| c.with_window(|w| w.with_title("我的应用")));
}

核心概念

1. RSX语法

Dioxus使用类似JSX的RSX语法:

rsx! {
    div {
        class: "my-class",
        id: "my-id",
        
        h1 { "标题" }
        p { "段落内容" }
        
        for item in items.iter() {
            ItemComponent { key: "{item.id}", item: item }
        }
    }
}

2. 状态管理

fn StatefulComponent(cx: Scope) -> Element {
    let mut text = use_state(cx, || String::new());
    
    cx.render(rsx! {
        div {
            input { 
                value: "{text}",
                oninput: move |e| text.set(e.value.clone())
            }
            p { "你输入了: {text}" }
        }
    })
}

3. 事件处理

fn EventHandling(cx: Scope) -> Element {
    cx.render(rsx! {
        button {
            onclick: move |_| println!("按钮被点击!"),
            onmouseenter: move |_| println!("鼠标进入"),
            "点击我"
        }
    })
}

高级特性

跨平台组件

#[derive(Props, PartialEq)]
struct UserCardProps {
    name: String,
    age: u8,
}

fn UserCard(cx: Scope<UserCardProps>) -> Element {
    cx.render(rsx! {
        div {
            class: "user-card",
            h2 { "{cx.props.name}" }
            p { "年龄: {cx.props.age}" }
        }
    })
}

条件渲染

fn ConditionalRendering(cx: Scope) -> Element {
    let show = use_state(cx, || true);
    
    cx.render(rsx! {
        div {
            button { onclick: move |_| show.set(!show), "切换" }
            
            if *show {
                p { "现在你看到我了!" }
            } else {
                p { "现在你看不到我了" }
            }
        }
    })
}

列表渲染

fn ListRendering(cx: Scope) -> Element {
    let items = use_state(cx, || vec!["苹果", "香蕉", "橙子"]);
    
    cx.render(rsx! {
        ul {
            for item in items.iter() {
                li { key: "{item}", "{item}" }
            }
        }
    })
}

性能优化

Dioxus通过以下方式实现高效渲染:

  1. 虚拟DOM差异算法
  2. 记忆化组件
  3. 细粒度状态更新
// 使用memo优化组件
fn OptimizedComponent(cx: Scope) -> Element {
    let count = use_state(cx, || 0);
    
    cx.render(rsx! {
        div {
            ExpensiveComponent { count: count }
            button { onclick: move |_| count += 1, "增加" }
        }
    })
}

#[inline_props]
fn ExpensiveComponent(cx: Scope, count: &i32) -> Element {
    // 这个组件只在count变化时重新渲染
    cx.render(rsx! {
        p { "计数: {count}" }
    })
}

跨平台开发

Dioxus支持多种平台目标:

Web目标

// 使用dioxus-web
fn main() {
    dioxus_web::launch(App);
}

桌面目标

// 使用dioxus-desktop
fn main() {
    dioxus_desktop::launch(App);
}

静态站点生成

// 使用dioxus-ssr
fn main() {
    let html = dioxus_ssr::render_lazy(App);
    // 保存到文件或作为服务器响应
}

生态系统

Dioxus提供了一系列周边库:

  • dioxus-router:路由解决方案
  • dioxus-use:常用hooks集合
  • dioxus-material:Material UI组件
  • dioxus-bootstrap:Bootstrap样式组件

完整示例:Todo应用

下面是内容中提供的完整Todo应用示例:

use dioxus::prelude::*;

fn main() {
    dioxus_web::launch(App);
}

#[derive(Clone, Debug)]
struct TodoItem {
    id: usize,
    title: String,
    completed: bool,
}

fn App(cx: Scope) -> Element {
    let todos = use_ref(cx, || vec![
        TodoItem { id: 1, title: "学习Rust".to_string(), completed: false },
        TodoItem { id: 2, title: "尝试Dioxus".to_string(), completed: true },
    ]);
    
    let new_todo = use_state(cx, || String::new());
    
    cx.render(rsx! {
        div {
            h1 { "Todo应用" }
            
            // 添加新todo
            div {
                input {
                    value: "{new_todo}",
                    oninput: move |e| new_todo.set(e.value.clone()),
                    placeholder: "输入新任务"
                }
                button {
                    onclick: move |_| {
                        if !new_todo.is_empty() {
                            let mut todos = todos.write();
                            todos.push(TodoItem {
                                id: todos.len() + 1,
                                title: new_todo.get().clone(),
                                completed: false,
                            });
                            new_todo.set(String::new());
                        }
                    },
                    "添加"
                }
            }
            
            // 显示todo列表
            ul {
                for todo in todos.read().iter() {
                    li {
                        key: "{todo.id}",
                        input {
                            r#type: "checkbox",
                            checked: todo.completed,
                            onchange: move |e| {
                                let mut todos = todos.write();
                                if let Some(t) = todos.iter_mut().find(|t| t.id == todo.id) {
                                    t.completed = e.value.parse().unwrap();
                                }
                            }
                        }
                        span {
                            style: if todo.completed { "text-decoration: line-through" } else { "" },
                            "{todo.title}"
                        }
                        button {
                            onclick: move |_| {
                                todos.write().retain(|t| t.id != todo.id);
                            },
                            "删除"
                        }
                    }
                }
            }
        }
    })
}

扩展示例:增强版Todo应用

以下是一个增强版的Todo应用示例,包含更多功能:

use dioxus::prelude::*;

fn main() {
    dioxus_web::launch(App);
}

#[derive(Clone, Debug)]
struct TodoItem {
    id: usize,
    title: String,
    completed: bool,
    created_at: chrono::DateTime<chrono::Local>,
}

fn App(cx: Scope) -> Element {
    let todos = use_ref(cx, || {
        vec![
            TodoItem {
                id: 1,
                title: "学习Rust".to_string(),
                completed: false,
                created_at: chrono::Local::now(),
            },
            TodoItem {
                id: 2,
                title: "尝试Dioxus".to_string(),
                completed: true,
                created_at: chrono::Local::now(),
            },
        ]
    });

    let new_todo = use_state(cx, || String::new());
    let filter = use_state(cx, || "all".to_string());

    cx.render(rsx! {
        div {
            style: "max-width: 600px; margin: 0 auto; padding: 20px;",
            h1 { style: "text-align: center;", "Todo应用" }
            
            // 过滤选项
            div {
                style: "display: flex; gap: 10px; margin-bottom: 20px;",
                button {
                    onclick: move |_| filter.set("all".to_string()),
                    style: "padding: 5px 10px;",
                    "全部"
                }
                button {
                    onclick: move |_| filter.set("active".to_string()),
                    style: "padding: 5px 10px;",
                    "未完成"
                }
                button {
                    onclick: move |_| filter.set("completed".to_string()),
                    style: "padding: 5px 10px;",
                    "已完成"
                }
            }
            
            // 添加新todo
            div {
                style: "display: flex; gap: 10px; margin-bottom: 20px;",
                input {
                    style: "flex-grow: 1; padding: 8px;",
                    value: "{new_todo}",
                    oninput: move |e| new_todo.set(e.value.clone()),
                    placeholder: "输入新任务",
                    onkeydown: move |e| {
                        if e.key == "Enter" && !new_todo.is_empty() {
                            let mut todos = todos.write();
                            todos.push(TodoItem {
                                id: todos.len() + 1,
                                title: new_todo.get().clone(),
                                completed: false,
                                created_at: chrono::Local::now(),
                            });
                            new_todo.set(String::new());
                        }
                    }
                }
                button {
                    style: "padding: 8px 16px;",
                    onclick: move |_| {
                        if !new_todo.is_empty() {
                            let mut todos = todos.write();
                            todos.push(TodoItem {
                                id: todos.len() + 1,
                                title: new_todo.get().clone(),
                                completed: false,
                                created_at: chrono::Local::now(),
                            });
                            new_todo.set(String::new());
                        }
                    },
                    "添加"
                }
            }
            
            // 显示todo列表
            ul {
                style: "list-style: none; padding: 0;",
                for todo in todos.read().iter().filter(|todo| match filter.get().as_str() {
                    "all" => true,
                    "active" => !todo.completed,
                    "completed" => todo.completed,
                    _ => true,
                }) {
                    li {
                        key: "{todo.id}",
                        style: "display: flex; align-items: center; gap: 10px; padding: 10px; border-bottom: 1px solid #eee;",
                        input {
                            r#type: "checkbox",
                            checked: todo.completed,
                            onchange: move |e| {
                                let mut todos = todos.write();
                                if let Some(t) = todos.iter_mut().find(|t| t.id == todo.id) {
                                    t.completed = e.value.parse().unwrap();
                                }
                            }
                        }
                        div {
                            style: "flex-grow: 1;",
                            span {
                                style: if todo.completed { "text-decoration: line-through; color: #999;" } else { "" },
                                "{todo.title}"
                            }
                            div {
                                style: "font-size: 0.8em; color: #666;",
                                "创建于: {todo.created_at.format("%Y-%m-%d %H:%M")}"
                            }
                        }
                        button {
                            style: "padding: 5px 10px;",
                            onclick: move |_| {
                                todos.write().retain(|t| t.id != todo.id);
                            },
                            "删除"
                        }
                    }
                }
            }
            
            // 统计信息
            div {
                style: "margin-top: 20px; text-align: right; color: #666;",
                "总计: {todos.read().len()} | ",
                "已完成: {todos.read().iter().filter(|t| t.completed).count()} | ",
                "未完成: {todos.read().iter().filter(|t| !t.completed).count()}"
            }
        }
    })
}

Dioxus为Rust开发者提供了一种现代化的UI开发方式,结合了Rust的性能优势与声明式UI的开发效率,是构建跨平台应用的优秀选择。

回到顶部