组件属性(Props)
就像你可以向函数传递参数或向元素传递属性一样,你可以向组件传递属性(props),以自定义其行为!我们到目前为止看到的组件都没有接受任何属性——那么让我们编写一些接受属性的组件。
#[derive(Props)]
组件属性是一个用#[derive(PartialEq, Clone, Props)]
注解的单个结构体。为了让组件接受属性,其参数的类型必须是YourPropsStruct
。
示例:
// 记住:拥有属性必须实现`PartialEq`!
#[derive(PartialEq, Props, Clone)]
struct LikesProps {
score: i32,
}
fn Likes(props: LikesProps) -> Element {
rsx! {
div {
"This post has "
b { "{props.score}" }
" likes"
}
}
}
然后,你可以像传递元素的属性一样向组件传递属性值:
pub fn App() -> Element {
rsx! { Likes { score: 42 } }
}
属性选项
#[derive(Props)]
宏有一些特性,可以让你自定义属性的行为。
可选属性
你可以通过为字段使用Option<…>
类型来创建可选字段:
#[derive(PartialEq, Clone, Props)]
struct OptionalProps {
title: String,
subtitle: Option<String>,
}
fn Title(props: OptionalProps) -> Element {
rsx! {
h1 { "{props.title}: ", {props.subtitle.unwrap_or_else(|| "No subtitle provided".to_string())} }
}
}
然后,你可以选择提供它们或不提供:
Title { title: "Some Title" }
Title { title: "Some Title", subtitle: "Some Subtitle" }
// 显式提供Option不会编译:
// Title {
// title: "Some Title",
// subtitle: None,
// },
显式必需的Option
如果你想显式要求一个Option
,而不是一个可选属性,你可以用#[props(!optional)]
来注解它:
#[derive(PartialEq, Clone, Props)]
struct ExplicitOptionProps {
title: String,
#[props(!optional)]
subtitle: Option<String>,
}
fn ExplicitOption(props: ExplicitOptionProps) -> Element {
rsx! {
h1 { "{props.title}: ", {props.subtitle.unwrap_or_else(|| "No subtitle provided".to_string())} }
}
}
然后,你必须显式传递Some("str")
或None
:
ExplicitOption { title: "Some Title", subtitle: None }
ExplicitOption { title: "Some Title", subtitle: Some("Some Title".to_string()) }
// 这不会编译:
// ExplicitOption {
// title: "Some Title",
// },
默认属性
你可以使用#[props(default = 42)]
使一个字段成为可选的,并指定其默认值:
#[derive(PartialEq, Props, Clone)]
struct DefaultProps {
// 当没有提供时,默认为42
#[props(default = 42)]
number: i64,
}
fn DefaultComponent(props: DefaultProps) -> Element {
rsx! { h1 { "{props.number}" } }
}
然后,类似于可选属性,你不必提供它:
DefaultComponent { number: 5 }
DefaultComponent {}
使用into自动转换
在Rust函数中,通常接受impl Into<SomeType>
而不是仅仅SomeType
,以支持更广泛的参数范围。如果你想在属性中使用类似的功能,你可以使用#[props(into)]
。例如,你可以在一个String
属性上添加它——并且&str
也会被自动接受,因为它可以被转换为String
:
#[derive(PartialEq, Props, Clone)]
struct IntoProps {
#[props(into)]
string: String,
}
fn IntoComponent(props: IntoProps) -> Element {
rsx! { h1 { "{props.string}" } }
}
然后,你可以这样使用它:
IntoComponent { string: "some &str" }
组件宏
到目前为止,我们见过的每个组件函数都有一个对应的ComponentProps结构体来传递属性。这相当冗长……如果有属性作为简单的函数参数,那不是很好吗?然后我们就不需要定义Props结构体了,而是可以直接使用whatever
而不是props.whatever
!
component
允许你这样做。而不是输入“完整”版本:
#[derive(Props, Clone, PartialEq)]
struct TitleCardProps {
title: String,
}
fn TitleCard(props: TitleCardProps) -> Element {
rsx!{
h1 { "{props.title}" }
}
}
……你可以定义一个接受属性作为参数的函数。然后,只需用#[component]
注解它,宏就会为你将其转换为常规组件:
#[component]
fn TitleCard(title: String) -> Element {
rsx!{
h1 { "{title}" }
}
}
虽然新的组件更短,更易读,但库作者不应该使用这个宏,因为你对属性文档的控制较少。
组件子元素
在某些情况下,你可能希望创建一个作为其他内容容器的组件,而不需要组件知道这些内容是什么。为了实现这一点,创建一个类型为Element
的属性:
#[derive(PartialEq, Clone, Props)]
struct ClickableProps {
href: String,
body: Element,
}
fn Clickable(props: ClickableProps) -> Element {
rsx! {
a { href: "{props.href}", class: "fancy-button", {props.body} }
}
}
然后,在渲染组件时,你可以传入rsx!{...}
的输出:
rsx! {
Clickable {
href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",
body: rsx! {
"How to " i { "not" } " be seen"
}
}
}
警告:虽然它可能编译,但不要在RSX中多次包含同一个
Element
。结果行为是未指定的。
children字段
而不是通过常规属性传递RSX,你可能希望像元素可以有子元素一样接受子元素。“魔法”的children
属性让你可以实现这一点:
#[derive(PartialEq, Clone, Props)]
struct ClickableProps {
href: String,
children: Element,
}
fn Clickable(props: ClickableProps) -> Element {
rsx! {
a { href: "{props.href}", class: "fancy-button", {props.children} }
}
}
这使得使用组件更加简单:只需将RSX放在{}
括号内——不需要render
调用或其他宏!
rsx! {
Clickable { href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",
"How to "
i { "not" }
" be seen"
}
}