RUST语言跨平台GUI框架Dioxus制作桌面应用入门教程之编写第一个组件(二)

本文介绍了如何使用Dioxus声明式框架创建一个动态显示故事帖子的组件,包括添加内容、设置属性、创建组件和Props,以及使用chrono和serde进行日期和序列化处理。
摘要由CSDN通过智能技术生成

本节将教您如何创建一个显示故事帖子的内容组件。

首先,让我们定义如何显示帖子。Dioxus 是一个声明式框架。这意味着我们不必告诉 Dioxus 要做什么(例如“创建一个元素”或“将颜色设置为红色”),而是简单地声明我们希望 UI 的外观。

创建简单显示的应用

要声明您希望 UI 的外观,您需要使用 rsx 宏。让我们修改入门部分中 App 函数中的 rsx 宏,以显示有关我们的故事的信息:

#![allow(non_snake_case)]
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*;

fn main() {
    // launch the dioxus app in a webview
    dioxus_desktop::launch(App);
}

pub fn App(cx: Scope) -> Element {
    render! {
        "story"
    }
}

运行后看到这样的界面:

动态显示内容

让我们扩展 App 组件,以包含故事标题、作者、分数、发布时间和评论数。我们可以在 render 宏中插入动态文本,方法是在 {} 中插入变量(这与 println! 宏中的格式化类似):

pub fn App(cx: Scope) -> Element {
    let title = "title";
    let by = "author";
    let score = 0;
    let time = chrono::Utc::now();
    let comments = "comments";

    render! {
        "{title} by {by} ({score}) {time} {comments}"
    }
}

这里的时间使用了chrono库,如果报错

PS D:\project\dioxus\demo> cargo run
   Compiling demo v0.1.0 (D:\project\dioxus\demo)
    Finished dev [unoptimized + debuginfo] target(s) in 2.84s
     Running `target\debug\demo.exe`
PS D:\project\dioxus\demo> cargo run
   Compiling demo v0.1.0 (D:\project\dioxus\demo)
error[E0433]: failed to resolve: use of undeclared crate or module `chrono`
  --> src\main.rs:14:16
   |
14 |     let time = chrono::Utc::now();
   |                ^^^^^^ use of undeclared crate or module `chrono`

For more information about this error, try `rustc --explain E0433`.
error: could not compile `demo` (bin "demo") due to previous error

使用以下命令安装一下: 

cargo add chrono   

运行结果:

创建元素

接下来,让我们将帖子描述包裹在一个 div 元素中。您可以通过在元素名称后面加上 { 并在元素的最后一个子元素后面加上 } 来在 Dioxus 中创建 HTML 元素:

pub fn App(cx: Scope) -> Element {
    let title = "title";
    let by = "author";
    let score = 0;
    let time = chrono::Utc::now();
    let comments = "comments";

    render! {
        div {
            "{title} by {by} ({score}) {time} {comments}"
        }
    }
}

运行结果:

看起来好像跟前面的结果没多大区别,这是因为html的默认显示。

设置属性

接下来,让我们为帖子列表添加一些 padding 属性。

属性(和监听器)会修改它们所附加的元素的行为或外观。它们在 {} 括号内指定,位于任何子元素之前,使用 name: value 语法。您可以像处理文本节点一样格式化属性中的文本:

pub fn App(cx: Scope) -> Element {
    let title = "title";
    let by = "author";
    let score = 0;
    let time = chrono::Utc::now();
    let comments = "comments";

    render! {
        div {
            padding: "0.5rem",
            position: "relative",
            "{title} by {by} ({score}) {time} {comments}"
        }
    }
}

运行结果:

 注意:dioxus-html中定义的所有属性都遵循snake_case(蛇形)命名约定。它们将snake_case名称转换为HTML的camelCase(驼峰)属性。

注意:样式可以直接在style:属性之外使用。在上面的例子中,color:“red”变成了style=“颜色:红色”您可以在属性引用中了解更多关于元素的信息

 创建组件

如果你不想在单个 main 函数中编写复杂的很长的程序,你也不应该在单个 App 函数中构建复杂的 UI。相反,你应该将应用程序的功能分解为称为组件。

组件是一个 Rust 函数,以 UpperCamelCase(首字母大写驼峰) 命名,它接受一个 Scope 参数并返回一个描述它想要渲染的 UI 的 Element。其实我们的 App 函数就是一个组件!

让我们将故事描述放到一个新组件中:

fn StoryListing(cx: Scope) -> Element {
    let title = "title";
    let by = "author";
    let score = 0;
    let time = chrono::Utc::now();
    let comments = "comments";

    render! {
        div {
            padding: "0.5rem",
            position: "relative",
            "{title} by {by} ({score}) {time} {comments}"
        }
    }
}

在app方法中:

pub fn App(cx: Scope) -> Element {
    render! {
        StoryListing {

        }
    }
}

执行后和上面结果一致。

创建Props

创建Props就像把参数传递给函数或把属性传递给元素一样,可以把props传递给组件来定制它的行为!通过在函数名称之前添加 #[component] 宏并添加额外的参数,我们可以定义组件在渲染时可以接受的参数(称为Props)。目前,我们的StoryListing组件只能展现相同的故事。

我们修改一下使他能接受一个故事作为Props来渲染。我们还将定义post,并包括如何使用不同的格式将我们的post转换为不同的格式的信息serde(Serde是一个rust框架,用于高效、通用地序列化和反序列化Rust数据结构)。

如果没有serde库,安装一下:

cargo add serde
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

// Define the Hackernews types
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StoryPageData {
    #[serde(flatten)]
    pub item: StoryItem,
    #[serde(default)]
    pub comments: Vec<Comment>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Comment {
    pub id: i64,
    /// there will be no by field if the comment was deleted
    #[serde(default)]
    pub by: String,
    #[serde(default)]
    pub text: String,
    #[serde(with = "chrono::serde::ts_seconds")]
    pub time: DateTime<Utc>,
    #[serde(default)]
    pub kids: Vec<i64>,
    #[serde(default)]
    pub sub_comments: Vec<Comment>,
    pub r#type: String,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StoryItem {
    pub id: i64,
    pub title: String,
    pub url: Option<String>,
    pub text: Option<String>,
    #[serde(default)]
    pub by: String,
    #[serde(default)]
    pub score: i64,
    #[serde(default)]
    pub descendants: i64,
    #[serde(with = "chrono::serde::ts_seconds")]
    pub time: DateTime<Utc>,
    #[serde(default)]
    pub kids: Vec<i64>,
    pub r#type: String,
}

#[component]
fn StoryListing(cx: Scope, story: StoryItem) -> Element {
    let StoryItem {
        title,
        by,
        score,
        time,
        kids,
        ..
    } = story;

    let comments = kids.len();

    render! {
        div {
            padding: "0.5rem",
            position: "relative",
            "{title} by {by} ({score}) {time} {comments}"
        }
    }
}

再来修改一下app函数:

pub fn App(cx: Scope) -> Element {
    render! {
        StoryListing {
            story: StoryItem {
                id: 0,
                title: "hello hackernews".to_string(),
                url: None,
                text: None,
                by: "Author".to_string(),
                score: 0,
                descendants: 0,
                time: chrono::Utc::now(),
                kids: vec![],
                r#type: "".to_string(),
            }
        }
    }
}

运行之后,结果和原来一致。

如果编译时报以下错误:
 

   Compiling demo v0.1.0 (D:\project\dioxus\demo)
error[E0433]: failed to resolve: could not find `serde` in `chrono`
  --> src\main.rs:22:20
   |
22 |     #[serde(with = "chrono::serde::ts_seconds")]
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ could not find `serde` in `chrono`

error[E0433]: failed to resolve: could not find `serde` in `chrono`
  --> src\main.rs:43:20
   |
43 |     #[serde(with = "chrono::serde::ts_seconds")]
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ could not find `serde` in `chrono`

error[E0601]: `main` function not found in crate `demo`
   --> src\main.rs:139:2
    |
139 | }
    |  ^ consider adding a `main` function to `src\main.rs`

Some errors have detailed explanations: E0433, E0601.
For more information about an error, try `rustc --explain E0433`.
error: could not compile `demo` (bin "demo") due to 3 previous errors

则执行以下命令:

cargo install -f cargo-whatfeatures

然后在Cargo.toml中修改

chrono = { version = "0.4.31", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }

运行结果如下图:

以下是完整代码:

配置文件:

#Cargo.toml 

[package]
name = "demo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
chrono = { version = "0.4.31", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
dioxus = "0.4.3"
dioxus-desktop = "0.4.3"

以下是全部代码:

use dioxus::prelude::*;

// 定义类型
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StoryPageData {
    #[serde(flatten)]
    pub item: StoryItem,
    #[serde(default)]
    pub comments: Vec<Comment>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Comment {
    pub id: i64,
    #[serde(default)]
    pub by: String,
    #[serde(default)]
    pub text: String,
    #[serde(with = "chrono::serde::ts_seconds")]
    pub time: DateTime<Utc>,
    #[serde(default)]
    pub kids: Vec<i64>,
    #[serde(default)]
    pub sub_comments: Vec<Comment>,
    pub r#type: String,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StoryItem {
    pub id: i64,
    pub title: String,
    pub url: Option<String>,
    pub text: Option<String>,
    #[serde(default)]
    pub by: String,
    #[serde(default)]
    pub score: i64,
    #[serde(default)]
    pub descendants: i64,
    #[serde(with = "chrono::serde::ts_seconds")]
    pub time: DateTime<Utc>,
    #[serde(default)]
    pub kids: Vec<i64>,
    pub r#type: String,
}

#[component]
fn StoryListing(cx: Scope, story: StoryItem) -> Element {
    let StoryItem {
        title,
        url,
        by,
        score,
        time,
        kids,
        id,
        ..
    } = story;

    let url = url.as_deref().unwrap_or_default();
    let hostname = url
        .trim_start_matches("https://")
        .trim_start_matches("http://")
        .trim_start_matches("www.");
    let score = format!("{score} {}", if *score == 1 { " point" } else { " points" });
    let comments = format!(
        "{} {}",
        kids.len(),
        if kids.len() == 1 {
            " comment"
        } else {
            " comments"
        }
    );
    let time = time.format("%D %l:%M %p");

    cx.render(rsx! {
        div {
            padding: "0.5rem",
            position: "relative",
            div {
                font_size: "1.5rem",
                a {
                    href: url,
                    "{title}"
                }
                a {
                    color: "gray",
                    href: "https://news.ycombinator.com/from?site={hostname}",
                    text_decoration: "none",
                    " ({hostname})"
                }
            }
            div {
                display: "flex",
                flex_direction: "row",
                color: "gray",
                div {
                    "{score}"
                }
                div {
                    padding_left: "0.5rem",
                    "by {by}"
                }
                div {
                    padding_left: "0.5rem",
                    "{time}"
                }
                div {
                    padding_left: "0.5rem",
                    "{comments}"
                }
            }
        }
    })
}


pub fn App(cx: Scope) -> Element {
    render! {
        StoryListing {
            story: StoryItem {
                id: 0,
                title: "hello hackernews".to_string(),
                url: None,
                text: None,
                by: "Author".to_string(),
                score: 0,
                descendants: 0,
                time: Utc::now(),
                kids: vec![],
                r#type: "".to_string(),
            }
        }
    }
}

fn main() {
    // launch the dioxus app in a webview
    dioxus_desktop::launch(App);
}

本人也是初学这个框架,找了iced/egui/bioxus等,别的配置麻烦,最终决定用这个框架写个小应用。分享与大家共勉!

  • 29
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值