学习目标
本课解决两个问题:根据条件决定显示什么,以及根据数据批量生成 UI。
知识点1:条件渲染 — if
最简单的场景:满足条件才显示某段 UI。
usedioxus::prelude::*;#[component]fnIfDemo()->Element{letis_logged_in=use_signal(||true);rsx!{// if 为真时渲染内部内容,为假时什么都不渲染ifis_logged_in(){div{class:"welcome","欢迎回来!"}}}}注意:
if 后面直接跟 { } 花括号,里面写 rsx! 的内容(不需要再套一层 rsx!)。
知识点2:条件渲染 — if / else
二选一:满足条件显示 A,否则显示 B
#[component]fnIfElseDemo()->Element{letis_vip=use_signal(||false);rsx!{ifis_vip(){div{class:"vip","VIP 专属内容"}}else{div{class:"normal","普通用户内容"}}}}知识点3:条件渲染 — if / else if / else
多分支:根据不同条件显示不同内容。
#[component]fnMultiBranchDemo()->Element{letrole=use_signal(||"editor");rsx!{ifrole()=="admin"{div{"管理员面板"}}elseifrole()=="editor"{div{"编辑面板"}}else{div{"访客面板"}}}}知识点4:条件渲染 — 提前计算 Element
当条件逻辑较复杂时,可以先在 Rust 代码中计算出 Element,再插入 rsx!:
#[component]fnPreComputeDemo()->Element{letstatus=use_signal(||404);rsx!{div{{letelement=matchstatus(){200=>rsx!{p{"页面正常"}},404=>rsx!{p{"页面未找到"}},500=>rsx!{p{"服务器错误"}},_=>rsx!{p{"未知状态"}},};element}}}}用 { } 包裹一个 Rust 代码块,代码块的最后一个表达式作为渲染结果插入。
知识点5:列表渲染 — for 循环
根据数据集合批量生成 UI 节点。
#[component]fnForDemo()->Element{letfruits=vec!["苹果","香蕉","橙子"];rsx!{ul{forfruitinfruits{li{key:"{fruit}",// key 帮助 diff 算法识别元素身份"{fruit}"}}}}}key 属性的作用:
- 列表重排、增删时,Dioxus 用 key 判断哪个节点是哪个
- 没有 key:按位置对比,重排后状态可能错乱
- 有 key:按标识对比,状态正确保留
- 规则:key 的值在列表中必须唯一
知识点6:列表渲染 — 带索引和复杂逻辑
#[component]fnForAdvancedDemo()->Element{letitems=vec!["Rust","Dioxus","WASM"];rsx!{// === 带索引 ===ol{for(index,item)initems.iter().enumerate(){li{key:"{item}","第 {index} 项:{item}"}}}// === 循环体中做复杂计算 ===div{foritemin&items{{letlabel=format!("🚀 {}",item.to_uppercase());rsx!{span{key:"{item}","{label} "}}}}}// === 迭代器直接渲染(不用 for 语法)===div{{(0..5).map(|i|rsx!{span{key:"{i}"," {i} "}})}}}}两种列表渲染写法对比:
写法 语法 适用场景
for 循环 for item in iter { … } 大多数场景,可读性好
迭代器表达式 {iter.map(… )} 需要链式调用(filter、enumerate 等)
知识点7:条件 + 列表组合
实际开发中,条件和列表经常组合使用:
#[component]fnCombinedDemo()->Element{letshow_list=use_signal(||true);letitems=vec!["任务A","任务B","任务C"];rsx!{div{ifshow_list(){// 条件为真时,渲染列表ul{foriteminitems{li{key:"{item}","{item}"}}}}else{p{"列表已隐藏"}}}}}核心规则
概念 说明
if 条件为真时渲染,为假时不渲染
if / else 二选一渲染
if / else if / else 多分支渲染
提前计算 Element 用 match 或 if 在 Rust 代码中算好 Element,用 { } 插入
for 循环 for item in iter { li { key: “…”, … } }
key 列表项唯一标识,帮助 diff 正确识别节点
迭代器渲染 {iter.map(\ x\ rsx! { … })} 写法
动手试试
补全下面的代码:
usedioxus::prelude::*;fnmain(){dioxus::launch(App);}#[component]fnApp()->Element{rsx!{div{style:"padding: 20px; font-family: sans-serif;",// === 题目1:登录状态显示 ===LoginDisplay{}hr{}// === 题目2:成绩等级 ===GradeDisplay{}hr{}// === 题目3:动态购物清单 ===ShoppingList{}}}}// === 题目1:登录状态显示 ===// 要求:// - 用 use_signal 存储 is_logged_in,初始值 false// - 用 use_signal 存储 username,初始值 "游客"// - 如果已登录:显示 "欢迎,{username}!" 和一个 "退出" 按钮// - 如果未登录:显示 "请先登录" 和一个 "登录" 按钮// - 点击 "登录" 按钮:is_logged_in 设为 true,username 设为 "张三"// - 点击 "退出" 按钮:is_logged_in 设为 false,username 设为 "游客"#[component]fnLoginDisplay()->Element{// 补全todo!()}// === 题目2:成绩等级 ===// 要求:// - 用 use_signal 存储 score,初始值 75// - 根据分数显示等级(用 if / else if / else):// - 90 及以上 → "优秀 🏆"// - 70~89 → "良好 👍"// - 60~69 → "及格 ✅"// - 60 以下 → "不及格 ❌"// - 显示当前分数和对应等级// - 提供 "+10" 和 "-10" 按钮调整分数#[component]fnGradeDisplay()->Element{// 补全todo!()}// === 题目3:动态购物清单 ===// 要求:// - 用 use_signal 存储商品列表 Vec<String>// 初始值:vec!["牛奶".into(), "面包".into(), "鸡蛋".into()]// - 用 for 循环渲染商品列表,每项带 key// - 每项后面有 "删除" 按钮,点击后从列表中移除该商品// - 底部显示 "共 N 件商品"// - 如果列表为空,显示 "购物车是空的"(用 if 判断)#[component]fnShoppingList()->Element{// 补全todo!()}期望效果
- 题目1:初始显示"请先登录"和"登录"按钮,点击登录后切换为"欢迎,张三!"和"退出"按钮
- 题目2:初始 75 分显示"良好 👍",点击 +10 变为 85 仍为"良好",再加 10 变为 95 显示"优秀 🏆"
- 题目3:初始显示 3 件商品,可逐个删除,删完后显示"购物车是空的"
写好了贴过来,没问题就进入第3课:组件 Props 与组件通信 👋
贴代码
Dioxus‑0.7 完整参考答案
Cargo.toml
[package] name = "dioxus-lesson" version = "0.1.0" edition = "2021" [dependencies] dioxus = "0.7"完整源码
usedioxus::prelude::*;fnmain(){dioxus::launch(App);}#[component]fnApp()->Element{rsx!{div{style:"padding: 20px; font-family: sans-serif;",// === 题目1:登录状态显示 ===LoginDisplay{}hr{}// === 题目2:成绩等级 ===GradeDisplay{}hr{}// === 题目3:动态购物清单 ===ShoppingList{}}}}// === 题目1:登录状态显示 ===#[component]fnLoginDisplay()->Element{letmutis_logged_in=use_signal(||false);letmutusername=use_signal(||"游客".to_string());rsx!{div{style:"padding:10px;border:1px solid #ccc;border-radius:6px;",ifis_logged_in(){p{"欢迎,{username}!"}button{onclick:move|_|{is_logged_in.set(false);username.set("游客".to_string());},"退出"}}else{p{"请先登录"}button{onclick:move|_|{is_logged_in.set(true);username.set("张三".to_string());},"登录"}}}}}// === 题目2:成绩等级 ===#[component]fnGradeDisplay()->Element{letmutscore=use_signal(||75u32);letgrade=matchscore(){90..=u32::MAX=>"优秀 🏆",70..=89=>"良好 👍",60..=69=>"及格 ✅",_=>"不及格 ❌",};rsx!{div{style:"padding:10px;border:1px solid #ccc;border-radius:6px;",p{"当前分数:{score}"}p{"等级:{grade}"}button{onclick:move|_|score+=10,"+10"}button{onclick:move|_|score-=10,style:"margin-left:8px","-10"}}}}// === 题目3:动态购物清单 ===#[component]fnShoppingList()->Element{letmutitems=use_signal(||vec!["牛奶".to_string(),"面包".to_string(),"鸡蛋".to_string(),]);rsx!{div{style:"padding:10px;border:1px solid #ccc;border-radius:6px;",ifitems().is_empty(){p{"购物车是空的"}}else{ul{for(idx,name)initems().iter().enumerate(){li{key:"{idx}","{name}"button{onclick:move|_|{items.remove(idx);},style:"margin-left:8px;color:red","删除"}}}}p{"共 {items().len()} 件商品"}}}}}核心知识点
1. 条件渲染:
if / else 直接写在 rsx! 内部,用于登录组件、购物清单空状态;
2. match模式匹配:
成绩等级用match做区间判断,比多个else‑if更简洁;
3. use_signal集合操作:items.remove(index) 删除数组元素,信号自动触发页面刷新;
4. 列表key:
循环渲染必须添加key属性,Dioxus diff算法需要。