第四章:组件通信——Props、回调与 Context
博客v1.0系列教程(Dioxus)博客 v1.0 系列教程 (Dioxus)
第四章:组件通信——Props、回调与 Context
1. 通信方式总览
| 方式 | 数据流向 | 适用场景 | |------|---------|---------| | Props | 父 → 子 | 直接传值 | | EventHandler | 子 → 父 | 子组件通知事件 | | children 插槽 | 父 → 子 | 传递子元素 | | Context | 跨层级 | 全局/共享状态 |
2. Props 传值
2.1 基础传值
#[component]
fn UserAvatar(name: String, size: u32, avatar_url: Option<String>) -> Element {
let size_class = format!("w-{} h-{}", size, size);
rsx! {
div { class: "flex items-center gap-2",
if let Some(url) = avatar_url {
img { class: "{size_class} rounded-full", src: "{url}" }
} else {
div { class: "{size_class} rounded-full bg-blue-500 flex items-center justify-center text-white",
"{name.chars().next().unwrap_or('?')}"
}
}
span { "{name}" }
}
}
}
// 使用
UserAvatar {
name: "张三".to_string(),
size: 10,
avatar_url: Some("/avatars/1.png".to_string()),
}
2.2 可选 Props 与默认值
#[component]
fn Badge(
text: String,
variant: Option<String>, // 可选:颜色变体
size: Option<String>, // 可选:尺寸
removable: Option<bool>, // 可选:是否可移除
) -> Element {
let variant = variant.unwrap_or_else(|| "default".to_string());
let size = size.unwrap_or_else(|| "md".to_string());
let removable = removable.unwrap_or(false);
let variant_class = match variant.as_str() {
"primary" => "bg-blue-100 text-blue-700",
"success" => "bg-green-100 text-green-700",
"danger" => "bg-red-100 text-red-700",
_ => "bg-gray-100 text-gray-700",
};
let size_class = match size.as_str() {
"sm" => "text-xs px-2 py-0.5",
"lg" => "text-sm px-3 py-1",
_ => "text-xs px-2.5 py-0.5",
};
rsx! {
span { class: "inline-flex items-center gap-1 rounded-full {variant_class} {size_class}",
"{text}"
if removable {
button { class: "hover:opacity-70", "×" }
}
}
}
}
3. EventHandler —— 子传父
3.1 基础回调
#[component]
fn InputField(
label: String,
on_change: EventHandler<String>, // 事件回调
) -> Element {
rsx! {
div { class: "mb-4",
label { class: "block text-sm mb-1", "{label}" }
input {
class: "border rounded px-3 py-2 w-full",
oninput: move |e| {
// 将 input 的值通过回调传递给父组件
on_change.call(e.value());
},
}
}
}
}
// 父组件
#[component]
fn RegisterForm() -> Element {
let username = use_signal(String::new);
let email = use_signal(String::new);
rsx! {
form {
InputField {
label: "用户名".to_string(),
on_change: move |val| username.set(val),
}
InputField {
label: "邮箱".to_string(),
on_change: move |val| email.set(val),
}
}
}
}
3.2 多参数回调
#[component]
fn Pagination(
on_page_change: EventHandler<(usize, usize)>, // (当前页, 总页数)
) -> Element {
rsx! {
button {
onclick: move |_| on_page_change.call((1, 10)),
"跳转到第1页"
}
}
}
3.3 无参数事件
#[component]
fn DeleteButton(on_confirm: EventHandler<()>) -> Element {
rsx! {
button {
class: "text-red-500",
onclick: move |_| on_confirm.call(()),
"删除"
}
}
}
// 使用
DeleteButton {
on_confirm: move |_| println!("确认删除"),
}
4. Children 插槽
#[component]
fn Panel(title: String, children: Element) -> Element {
rsx! {
div { class: "border rounded-lg overflow-hidden",
style: "background: var(--card); border-color: var(--border);",
div { class: "px-4 py-3 font-semibold border-b",
style: "background: var(--hover); border-color: var(--border);",
"{title}"
}
div { class: "p-4",
{children}
}
}
}
}
// 使用:将子元素传入插槽
Panel { title: "文章列表".to_string(),
ul {
li { "文章 1" }
li { "文章 2" }
li { "文章 3" }
}
}
5. Context 跨层级共享
当数据需要跨越多个组件层级时,逐层传递 Props 会变得冗余。Context 解决了这个问题。
5.1 提供 Context
#[component]
fn App() -> Element {
// 在顶层提供主题配置
let theme = use_signal(|| "light".to_string());
use_context_provider(|| theme);
rsx! {
NavBar {}
Content {}
}
}
5.2 消费 Context
#[component]
fn ThemeToggle() -> Element {
// 任意深度的子组件都可以直接读取
let mut theme = use_context::<Signal<String>>();
rsx! {
button {
onclick: move |_| {
let next = if theme() == "light" { "dark" } else { "light" };
theme.set(next.to_string());
},
"切换到{theme}"
}
}
}
5.3 多个 Context
// 可以同时提供多个 Context
#[component]
fn Providers(children: Element) -> Element {
let user = use_signal(|| Option::<User>::None);
let theme = use_signal(|| "light".to_string());
let lang = use_signal(|| "zh-CN".to_string());
use_context_provider(|| user);
use_context_provider(|| theme);
use_context_provider(|| lang);
rsx! { {children} }
}
6. 通信方式选择指南
需要传递数据?
├── 只在父子之间
│ ├── 父→子 → Props
│ └── 子→父 → EventHandler
├── 兄弟组件
│ ├── 简单 → 提升状态到共同父级
│ └── 复杂 → Context
├── 跨多层组件 → Context
└── 子元素插入 → children 插槽
7. 小结
- Props 是父传子的基本方式,支持必填和可选参数
EventHandler实现子传父的事件通知children插槽让父组件控制子元素的渲染use_context_provider+use_context实现跨层级共享- 选择合适的通信方式保持组件树的简洁
- 下一章将学习列表渲染与条件渲染
dioxuspropsevent-handlercontextchildren