描述用户界面
Dioxus是一个_声明式_框架。这意味着我们不是告诉Dioxus要做什么(例如“创建一个元素”或“将颜色设置为红色”),而是简单地使用RSX_声明_我们希望UI看起来的样子。
你已经在“hello world”应用程序中看到了RSX语法的一个简单示例:
// 定义一个渲染包含文本 "Hello, world!" 的div的组件
fn App() -> Element {
rsx! { div { "Hello, world!" } }
}
在这里,我们使用rsx!宏来_声明_我们想要一个div元素,其中包含文本"Hello, world!"。Dioxus接收RSX并从中构建UI。
RSX特性
RSX与HTML非常相似,它使用属性和子元素来描述元素。下面是一个RSX中的空button元素,以及生成的HTML:
rsx! {
button {
// 属性 / 监听器
// 子元素
"Hello, World!"
}
}
属性
属性(和事件处理器)可以修改它们附加的元素的行为或外观。它们在{}大括号内使用name: value语法指定。你可以在RSX中直接提供字面量作为值:
rsx! {
img {
src: "https://avatars.githubusercontent.com/u/79236386?s=200&v=4",
class: "primary_button",
width: "10px"
}
}
一些属性,比如input元素的type属性,在Rust中单独使用时不会起作用。这是因为type是Rust的保留关键字。为了解决这个问题,Dioxus使用r#标识符:
rsx! { input { r#type: "text", color: "red" } }
注意:在
dioxus-html中定义的所有属性都遵循snake_case命名约定。它们将它们的snake_case名称转换为HTML的camelCase属性。
注意:样式可以直接在
style:属性之外使用。在上面的例子中,color: "red"被转换为style="color: red"。
条件属性
你还可以通过使用没有else分支的if语句来有条件地包含属性。这在只有在满足特定条件时才添加属性时非常有用:
let large_font = true;
rsx! { div { class: if large_font { "text-xl" }, "Hello, World!" } }
自定义属性
Dioxus有一组预配置的属性,你可以使用。RSX在编译时进行验证,以确保你没有指定一个无效的属性。如果你想用自定义属性名覆盖这种行为,请用引号括起属性:
rsx! { div { "style": "width: 20px; height: 20px; background-color: red;" } }
特殊属性
虽然大多数属性只是简单地传递给HTML,但有些属性有特殊行为。
HTML Escape Hatch
如果你正在处理预先渲染的资产、模板输出或JS库的输出,那么你可能想要直接传递HTML,而不是通过Dioxus。在这些情况下,请使用dangerous_inner_html。
例如,运送一个将Markdown转换为Dioxus的转换器可能会显著增加你的最终应用程序大小。相反,你会想要预先将你的Markdown渲染为HTML,然后将HTML直接包含在你的输出中。我们为Dioxus首页使用了这种方法:
// 这应该来自一个可信的来源
let contents = "live <b>dangerously</b>";
rsx! { div { dangerous_inner_html: "{contents}" } }
注意!这个属性被称为“dangerous_inner_html”,因为它是危险的将你不信任的数据传递给它。如果你不小心,你很容易向你的用户暴露跨站脚本(XSS)攻击。
如果你正在处理不可信的输入,请确保在将HTML传递给
dangerous_inner_html之前对其进行消毒——或者只是将其传递给文本元素以转义任何HTML标签。
布尔属性
大多数属性在渲染时会按照你提供的输入精确渲染。然而,有些属性被认为是“布尔”属性,它们的存在决定了它们是否影响输出。对于这些属性,提供的值为"false"将导致它们从目标元素中被移除。
所以这个RSX实际上不会渲染hidden属性:
rsx! { div { hidden: false, "hello" } }
然而,并非所有属性都这样工作。_只有以下属性_具有这种行为:
allowfullscreenallowpaymentrequestasyncautofocusautoplaycheckedcontrolsdefaultdeferdisabledformnovalidatehiddenismapitemscopeloopmultiplemutednomodulenovalidateopenplaysinlinereadonlyrequiredreversedselectedtruespeed
对于任何其他属性,"false"的值将直接发送到DOM。
插值
类似于你可以在Rust字符串中格式化,你也可以在RSX文本中进行插值。使用{variable}在字符串中显示变量的值,或使用{variable:?}使用Debug表示:
let coordinates = (42, 0);
let country = "es";
rsx! {
div {
class: "country-{country}",
left: "{coordinates.0:?}",
top: "{coordinates.1:?}",
// 允许使用任意表达式,
// 只要它们不包含 `{}`
div { "{country.to_uppercase()}" }
div { "{7*6}" }
// `{}`可以用`{{}}`转义
div { "{{}}" }
}
}
子元素
要向元素添加子元素,将它们放在元素的{}大括号内所有属性和监听器之后。它们可以是其他元素、文本或组件。例如,你可以有一个ol(有序列表)元素,其中包含3个li(列表项)元素,每个元素都包含一些文本:
rsx! {
ol {
li { "First Item" }
li { "Second Item" }
li { "Third Item" }
}
}
- 第一项
- 第二项
- 第三项
Fragments
你可以在rsx!的顶级渲染多个元素,它们将自动分组。
rsx! {
p { "First Item" }
p { "Second Item" }
}
表达式
你可以在RSX中通过用{}包围你的表达式来包含任意Rust表达式作为子元素。任何实现了IntoDynNode的表达式都可以在rsx中使用。这对于显示来自迭代器的数据非常有用:
let text = "Dioxus";
rsx! {
span {
{text.to_uppercase()},
// 从0到9创建一个文本列表
{(0..10).map(|i| rsx!{ "{i}" })}
}
}
DIOXUS0123456789
循环
除了迭代器,你还可以直接在RSX中使用for循环:
rsx! {
// 使用for循环,其中体本身是RSX
div {
// 从0到9创建一个文本列表
for i in 0..3 {
// 注意:循环的体是RSX不是rust语句
div { "{i}" }
}
}
// 迭代器等效
div { {(0..3).map(|i| rsx!{ div { "{i}" } })} }
}
If语句
你还可以在使用没有else分支的if语句在RSX中:
rsx! {
// 使用没有else的if语句
if true {
div { "true" }
}
}