Estado de compartilhamento
Muitas vezes, vários componentes precisam acessar o mesmo estado. Dependendo de suas necessidades, existem várias maneiras de implementar isso.
Elevenado o Estado
Uma abordagem para compartilhar o estado entre os componentes é "elevá-lo" até o ancestral comum mais próximo. Isso significa colocar o hook use_state
em um componente pai e passar os valores necessários como props.
Por exemplo, suponha que queremos construir um editor de memes. Queremos ter uma entrada para editar a legenda do meme, mas também uma visualização do meme com a legenda. Logicamente, o meme e a entrada são 2 componentes separados, mas precisam acessar o mesmo estado (a legenda atual).
Claro, neste exemplo simples, poderíamos escrever tudo em um componente - mas é melhor dividir tudo em componentes menores para tornar o código mais reutilizável e fácil de manter (isso é ainda mais importante para aplicativos maiores e complexos) .
Começamos com um componente Meme
, responsável por renderizar um meme com uma determinada legenda:
#![allow(unused)] fn main() { #[inline_props] fn Meme<'a>(cx: Scope<'a>, caption: &'a str) -> Element<'a> { let container_style = r#" position: relative; width: fit-content; "#; let caption_container_style = r#" position: absolute; bottom: 0; left: 0; right: 0; padding: 16px 8px; "#; let caption_style = r" font-size: 32px; margin: 0; color: white; text-align: center; "; cx.render(rsx!( div { style: "{container_style}", img { src: "https://i.imgflip.com/2zh47r.jpg", height: "500px", }, div { style: "{caption_container_style}", p { style: "{caption_style}", "{caption}" } } } )) } }
Observe que o componente
Meme
não sabe de onde vem a legenda – ela pode ser armazenada emuse_state
,use_ref
ou uma constante. Isso garante que seja muito reutilizável - o mesmo componente pode ser usado para uma galeria de memes sem nenhuma alteração!
Também criamos um editor de legendas, totalmente desacoplado do meme. O editor de legendas não deve armazenar a legenda em si – caso contrário, como iremos fornecê-la ao componente Meme
? Em vez disso, ele deve aceitar a legenda atual como um suporte, bem como um manipulador de eventos para delegar eventos de entrada para:
#![allow(unused)] fn main() { #[inline_props] fn CaptionEditor<'a>( cx: Scope<'a>, caption: &'a str, on_input: EventHandler<'a, FormEvent>, ) -> Element<'a> { let input_style = r" border: none; background: cornflowerblue; padding: 8px 16px; margin: 0; border-radius: 4px; color: white; "; cx.render(rsx!(input { style: "{input_style}", value: "{caption}", oninput: move |event| on_input.call(event), })) } }
Finalmente, um terceiro componente renderizará os outros dois como filhos. Ele será responsável por manter o estado e passar os props relevantes.
#![allow(unused)] fn main() { fn MemeEditor(cx: Scope) -> Element { let container_style = r" display: flex; flex-direction: column; gap: 16px; margin: 0 auto; width: fit-content; "; let caption = use_state(cx, || "me waiting for my rust code to compile".to_string()); cx.render(rsx! { div { style: "{container_style}", h1 { "Meme Editor" }, Meme { caption: caption, }, CaptionEditor { caption: caption, on_input: move |event: FormEvent| {caption.set(event.value.clone());}, }, } }) } }
Usando o contexto
Às vezes, algum estado precisa ser compartilhado entre vários componentes na árvore, e passá-lo pelos props é muito inconveniente.
Suponha agora que queremos implementar uma alternância de modo escuro para nosso aplicativo. Para conseguir isso, faremos com que cada componente selecione o estilo dependendo se o modo escuro está ativado ou não.
Nota: estamos escolhendo esta abordagem como exemplo. Existem maneiras melhores de implementar o modo escuro (por exemplo, usando variáveis CSS). Vamos fingir que as variáveis CSS não existem – bem-vindo a 2013!
Agora, poderíamos escrever outro use_state
no componente superior e passar is_dark_mode
para cada componente através de props. Mas pense no que acontecerá à medida que o aplicativo crescer em complexidade – quase todos os componentes que renderizam qualquer CSS precisarão saber se o modo escuro está ativado ou não – para que todos precisem do mesmo suporte do modo escuro. E cada componente pai precisará passá-lo para eles. Imagine como isso ficaria confuso e verboso, especialmente se tivéssemos componentes com vários níveis de profundidade!
A Dioxus oferece uma solução melhor do que esta "perfuração com hélice" – fornecendo contexto. O hook use_context_provider
é semelhante ao use_ref
, mas o torna disponível através do use_context
para todos os componentes filhos.
Primeiro, temos que criar um struct para nossa configuração de modo escuro:
#![allow(unused)] fn main() { struct DarkMode(bool); }
Agora, em um componente de nível superior (como App
), podemos fornecer o contexto DarkMode
para todos os componentes filhos:
#![allow(unused)] fn main() { use_shared_state_provider(cx, || DarkMode(false)); }
Como resultado, qualquer componente filho de App
(direto ou não), pode acessar o contexto DarkMode
.
#![allow(unused)] fn main() { let dark_mode_context = use_shared_state::<DarkMode>(cx); }
use_context
retornaOption<UseSharedState<DarkMode>>
aqui. Se o contexto foi fornecido, o valor éSome(UseSharedState)
, que você pode chamar.read
ou.write
, similarmente aUseRef
. Caso contrário, o valor éNone
.
Por exemplo, aqui está como implementaríamos a alternância do modo escuro, que lê o contexto (para determinar a cor que deve renderizar) e grava nele (para alternar o modo escuro):
#![allow(unused)] fn main() { pub fn DarkModeToggle(cx: Scope) -> Element { let dark_mode = use_shared_state::<DarkMode>(cx).unwrap(); let style = if dark_mode.read().0 { "color:white" } else { "" }; cx.render(rsx!(label { style: "{style}", "Dark Mode", input { r#type: "checkbox", oninput: move |event| { let is_enabled = event.value == "true"; dark_mode.write().0 = is_enabled; }, }, })) } }