Dioxus 中的 RSX

521 阅读5分钟

描述用户界面

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"
    }
}

img

一些属性,比如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" } }

然而,并非所有属性都这样工作。_只有以下属性_具有这种行为:

  • allowfullscreen
  • allowpaymentrequest
  • async
  • autofocus
  • autoplay
  • checked
  • controls
  • default
  • defer
  • disabled
  • formnovalidate
  • hidden
  • ismap
  • itemscope
  • loop
  • multiple
  • muted
  • nomodule
  • novalidate
  • open
  • playsinline
  • readonly
  • required
  • reversed
  • selected
  • truespeed

对于任何其他属性,"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" }
    }
}
  1. 第一项
  2. 第二项
  3. 第三项

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" }
    }
}