本节将教您如何创建一个显示故事帖子的内容组件。
首先,让我们定义如何显示帖子。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等,别的配置麻烦,最终决定用这个框架写个小应用。分享与大家共勉!