Props de Componentes

Assim como você pode passar argumentos para uma função, você pode passar props para um componente que personaliza seu comportamento! Os componentes que vimos até agora não aceitam props – então vamos escrever alguns componentes que aceitam.

#[derive(Props)]

Props de componente são uma única estrutura anotada com #[derive(Props)]. Para um componente aceitar props, o tipo de seu argumento deve ser Scope<YourPropsStruct>. Então, você pode acessar o valor das props usando cx.props.

Existem 2 tipos de estruturas Props:

  • props próprios:
    • Não tem uma vida útil associada
    • Implementam PartialEq, permitindo a memoização (se os props não mudarem, o Dioxus não renderizará novamente o componente)
  • props emprestados:
    • Emprestado de um componente pai
    • Não pode ser memoizado devido a restrições de tempo de vida (Rust's lifetime)

Props Próprios

Props próprios são muito simples – eles não emprestam nada. Exemplo:


#![allow(unused)]
fn main() {
// Remember: Owned props must implement `PartialEq`!
#[derive(PartialEq, Props)]
struct LikesProps {
    score: i32,
}

fn Likes(cx: Scope<LikesProps>) -> Element {
    cx.render(rsx! {
        div {
            "This post has ",
            b { "{cx.props.score}" },
            " likes"
        }
    })
}
}

Você pode então passar valores de prop para o componente da mesma forma que você passaria atributos para um elemento:


#![allow(unused)]
fn main() {
fn App(cx: Scope) -> Element {
    cx.render(rsx! {
        Likes {
            score: 42,
        },
    })
}
}

Screenshot: Likes component

Props Emprestados

Possuir props funciona bem se seus props forem fáceis de copiar – como um único número. Mas e se precisarmos passar um tipo de dados maior, como uma String de um componente App para um subcomponente TitleCard? Uma solução ingênua pode ser .clone() a String, criando uma cópia dela para o subcomponente – mas isso seria ineficiente, especialmente para Strings maiores.

Rust permite algo mais eficiente – emprestar a String como um &str – é para isso que servem as props emprestadas!


#![allow(unused)]
fn main() {
#[derive(Props)]
struct TitleCardProps<'a> {
    title: &'a str,
}

fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
    cx.render(rsx! {
        h1 { "{cx.props.title}" }
    })
}
}

Podemos então usar o componente assim:


#![allow(unused)]
fn main() {
fn App(cx: Scope) -> Element {
    let hello = "Hello Dioxus!";

    cx.render(rsx!(TitleCard { title: hello }))
}
}

Screenshot: TitleCard component

Props de Option

A macro #[derive(Props)] tem alguns recursos que permitem personalizar o comportamento dos adereços.

Props Opcionais

Você pode criar campos opcionais usando o tipo Option<…> para um campo:


#![allow(unused)]
fn main() {
#[derive(Props)]
struct OptionalProps<'a> {
    title: &'a str,
    subtitle: Option<&'a str>,
}

fn Title<'a>(cx: Scope<'a, OptionalProps>) -> Element<'a> {
    cx.render(rsx!(h1{
        "{cx.props.title}: ",
        cx.props.subtitle.unwrap_or("No subtitle provided"),
    }))
}
}

Em seguida, você pode optar por fornecê-los ou não:


#![allow(unused)]
fn main() {
Title {
    title: "Some Title",
},
Title {
    title: "Some Title",
    subtitle: "Some Subtitle",
},
// Providing an Option explicitly won't compile though:
// Title {
//     title: "Some Title",
//     subtitle: None,
// },
}

Option Explicitamente Obrigatórias

Se você quiser exigir explicitamente uma Option, e não uma prop opcional, você pode anotá-la com #[props(!optional)]:


#![allow(unused)]
fn main() {
#[derive(Props)]
struct ExplicitOptionProps<'a> {
    title: &'a str,
    #[props(!optional)]
    subtitle: Option<&'a str>,
}

fn ExplicitOption<'a>(cx: Scope<'a, ExplicitOptionProps>) -> Element<'a> {
    cx.render(rsx!(h1 {
        "{cx.props.title}: ",
        cx.props.subtitle.unwrap_or("No subtitle provided"),
    }))
}
}

Então, você tem que passar explicitamente Some("str") ou None:


#![allow(unused)]
fn main() {
ExplicitOption {
    title: "Some Title",
    subtitle: None,
},
ExplicitOption {
    title: "Some Title",
    subtitle: Some("Some Title"),
},
// This won't compile:
// ExplicitOption {
//     title: "Some Title",
// },
}

Props Padrão

Você pode usar #[props(default = 42)] para tornar um campo opcional e especificar seu valor padrão:


#![allow(unused)]
fn main() {
#[derive(PartialEq, Props)]
struct DefaultProps {
    // default to 42 when not provided
    #[props(default = 42)]
    number: i64,
}

fn DefaultComponent(cx: Scope<DefaultProps>) -> Element {
    cx.render(rsx!(h1 { "{cx.props.number}" }))
}
}

Então, da mesma forma que props opcionais, você não precisa fornecê-lo:


#![allow(unused)]
fn main() {
DefaultComponent {
    number: 5,
},
DefaultComponent {},
}

Conversão Automática com .into

É comum que as funções Rust aceitem impl Into<SomeType> em vez de apenas SomeType para suportar uma ampla gama de parâmetros. Se você quiser uma funcionalidade semelhante com props, você pode usar #[props(into)]. Por exemplo, você pode adicioná-lo em uma prop String – e &str também será aceito automaticamente, pois pode ser convertido em String:


#![allow(unused)]
fn main() {
#[derive(PartialEq, Props)]
struct IntoProps {
    #[props(into)]
    string: String,
}

fn IntoComponent(cx: Scope<IntoProps>) -> Element {
    cx.render(rsx!(h1 { "{cx.props.string}" }))
}
}

Então, você pode usá-lo assim:


#![allow(unused)]
fn main() {
IntoComponent {
    string: "some &str",
},
}

A macro inline_props

Até agora, todas as funções Component que vimos tinham uma struct ComponentProps correspondente para passar em props. Isso foi bastante verboso... Não seria legal ter props como argumentos de função simples? Então não precisaríamos definir uma estrutura Props, e ao invés de digitar cx.props.whatever, poderíamos usar whatever diretamente!

inline_props permite que você faça exatamente isso. Em vez de digitar a versão "completa":


#![allow(unused)]
fn main() {
#[derive(Props, PartialEq)]
struct TitleCardProps {
    title: String,
}

fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
    cx.render(rsx!{
        h1 { "{cx.props.title}" }
    })
}
}

...você pode definir uma função que aceita props como argumentos. Então, basta anotá-lo com #[inline_props], e a macro irá transformá-lo em um componente regular para você:


#![allow(unused)]
fn main() {
#[inline_props]
fn TitleCard(cx: Scope, title: String) -> Element {
    cx.render(rsx!{
        h1 { "{title}" }
    })
}
}

Embora o novo Componente seja mais curto e fácil de ler, essa macro não deve ser usada por autores de bibliotecas, pois você tem menos controle sobre a documentação do Prop.