互动性
现在,我们的 HotDog 应用程序已经搭好了脚手架并设计好了样式,我们终于可以添加一些交互元素了。
封装状态
在进一步深入之前,我们先将应用程序分成两部分:Title 和 DogView 。这将有助于我们组织应用程序,并将 DogView 状态与 Title 状态分开。
rust
#[component]
fn App() -> Element {
rsx! {
document::Stylesheet { href: CSS }
Title {}
DogView {}
}
}
#[component]
fn Title() -> Element {
rsx! {
div { id: "title",
h1 { "HotDog! 🌭" }
}
}
}
#[component]
fn DogView() -> Element {
rsx! {
div { id: "dogview",
img { src: "https://images.dog.ceo/breeds/pitbull/dog-3981540_1280.jpg" }
}
div { id: "buttons",
button { id: "skip", "skip" }
button { id: "save", "save!" }
}
}
}
事件处理程序
在 DogView 组件中,我们希望为点击按钮附加一个动作。例如:跳过或保存当前的狗狗照片。我们可以使用 EventHandler 来监听点击事件。
事件处理程序与常规属性类似,但它们的名称通常以 on 开头,并接受闭包作为值。每当触发相应事件时,闭包就会被调用。监听器会接收事件对象中的事件信息。
我们将添加一些闭包,然后将它们传递给跳过和保存按钮的 onclick 属性:
rust
#[component]
fn DogView() -> Element {
let skip = move |evt| {};
let save = move |evt| {};
rsx! {
// ...
div { id: "buttons",
button { onclick: skip, id: "skip", "skip" }
button { onclick: save, id: "save", "save!" }
}
}
}
有关事件处理程序的更多信息,请参阅事件处理程序参考资料
使用use_hook
的状态
到目前为止,我们的组件还没有内部状态。对于 DogView,我们希望在用户点击跳过或保存时更改当前显示的狗狗照片。
为了在组件中存储状态,Dioxus 提供了 use_hook
函数。这使得裸 Rust 函数可以存储和加载状态,而无需使用额外的结构体。
在组件中调用时,use_hook
函数将返回一个原始存储值的 .clone()
:
rust
#[component]
fn DogView() -> Element {
let img_src = use_hook(|| "https://images.dog.ceo/breeds/pitbull/dog-3981540_1280.jpg");
// ..
rsx! {
div { id: "dogview",
img { src: "{img_src}" }
}
// ..
}
}
Dioxus 钩子与 React 的钩子非常相似,需要遵循一些简单的规则才能正常运行。
信号和使用信号
虽然 use_hook
可以存储任何实现了 Clone 的值,但您经常需要一种更强大的状态管理方式。Dioxus 内置了信号。
信号 是普通 Rust 值的封装类型,它可以跟踪读写,让你的应用程序栩栩如生。你可以在信号中封装任何 Rust 值。信号可以通过 Signal::new()
手动创建,但我们强烈建议使用 use_signal
钩子。
手动创建信号需要记住在信号上调用
.manually_drop()
,而use_signal
会自动为你清理信号。
每当信号的值发生变化时,其包含的 "反应范围 "就会被 "标记为脏 "并重新运行。默认情况下,Dioxus 组件都是反应式作用域,因此只要信号值发生变化,就会重新渲染。

信号是 Dioxus 的核心 ,需要时间来掌握。我们建议您在开发第一个大型应用程序之前深入阅读状态管理指南。
全局状态与上下文
虽然钩子能很好地管理组件的本地状态,但有时您也需要管理整个应用程序的状态。
Dioxus 提供了两种机制:上下文(Context )和全局信号(GlobalSignal)。
通过 Context API,父组件可以与子组件共享状态,而无需明确声明额外的属性字段。大型应用程序和程序库可利用这种机制在整个应用程序中共享状态,而无需修改组件签名。
要 "provide "上下文,只需使用实现 Clone 的结构调用 use_context_provider()
。要读取子代中的上下文,请调用 use_context()
。
rust
// Create a new wrapper type
#[derive(Clone)]
struct TitleState(String);
fn App() -> Element {
// Provide that type as a Context
use_context_provider(|| TitleState("HotDog".to_string()));
rsx! {
Title {}
}
}
fn Title() -> Element {
// Consume that type as a Context
let title = use_context::<TitleState>();
rsx! {
h1 { "{title.0}" }
}
}
您可以将 use_signal
和 Context
结合起来,为应用程序提供反应状态:
rust
#[derive(Clone, Copy)]
struct MusicPlayer {
song: Signal<String>,
}
fn use_music_player_provider() {
let song = use_signal(|| "Drift Away".to_string());
use_context_provider(|| MusicPlayer { song });
}
通过 use_context
和 consume_context
,您可以轻松地修改该状态:
rust
#[component]
fn Player() -> Element {
rsx! {
button {
onclick: move |_| consume_context::<MusicPlayer>().song.set("Vienna".to_string()),
"Shuffle"
}
}
}
当数值发生变化时,读取歌曲信号的任何组件都会自动重新渲染。
全局信号
有时您需要一个简单的全局值。这时,全局信号(GlobalSignal )就能帮上忙。GlobalSignals 是上下文系统和信号的组合,不需要额外的结构或设置。
只需在应用程序中声明一个 GlobalSignal 即可:
rust
static SONG: GlobalSignal<String> = Signal::global(|| "Drift Away".to_string());
然后在任何地方读写:
rust
#[component]
fn Player() -> Element {
rsx! {
h3 { "Now playing {SONG}" }
button {
onclick: move |_| *SONG.write() = "Vienna".to_string(),
"Shuffle"
}
}
}
📣 GlobalSignals 只对一个应用程序具有全局性,而不是整个程序。在服务器上,每个应用程序都有自己的 GlobalSignal。
对于 HotDog 来说,我们不需要 GlobalSignal 或 Context,但重要的是要知道您可以使用这些功能。