SwiftUI 系列 08|List 的常见坑:刷新、删除、跳转、性能问题
List 真正难的地方不是控件会不会用,而是数据身份、状态更新和交互行为一旦纠缠就很容易一起出问题
List 是 SwiftUI 里最容易“看起来很好写”,也最容易在真实业务里踩坑的控件之一。
因为很多简单例子都很顺:
- 数据一丢进去就能显示
- 下拉刷新也能接
- 删除、跳转、分区似乎都有现成能力
但一旦项目里开始出现:
- 分页
- 搜索
- 删除后状态联动
- 跳转详情再返回
- 图片加载
List 的问题就会逐渐暴露出来。
一、很多 List 问题的根源,不是控件复杂,而是“数据身份”没想清楚
SwiftUI 的很多行为都和 identity 强相关。
如果你的列表数据“看起来一样”,但身份不稳定,后面就很容易出现:
- 刷新时跳动
- 删除时动画奇怪
- 返回列表后滚动位置异常
- 某一行内容刷新不对
所以 List 最基础也最容易被低估的一件事,不是样式,而是:
- 每个 item 的身份是不是稳定
- 这个身份在刷新、分页、搜索切换时是否仍然可信
很多列表“玄学 bug”,最后根因都在这里。
二、刷新和分页为什么经常会把列表状态搞乱
因为刷新和分页本质上是在修改“当前列表集合”,而集合变化会直接影响:
- cell 复用关系
- 滚动位置
- 当前 loading 状态
- 空态与内容态切换
如果这些关系没理清,就很容易出现:
- 下拉刷新时列表闪一下
- 分页回来整个列表重算
- 旧结果覆盖新内容
所以 List 真正的难点经常不是“怎么接 refreshable”,而是:
- 刷新时哪些状态该保留
- 分页时哪些状态该追加
- 同一类请求之间是否会并发冲突
三、删除操作为什么经常不是“删掉一个元素”那么简单
表面上看,删除只是在数组里移掉一个元素。
但真实列表里,删除往往同时牵动这些东西:
- 当前展示集合
- 空态判断
- 选中态
- 跳转状态
- 后端同步状态
如果只是“先删再说”,很容易出现:
- 删除成功了,但详情页还指向旧对象
- 删除失败时列表和服务端状态不一致
- 动画没问题,但数据源回滚逻辑很混乱
这说明删除的真正难点不是 UI 操作,而是:
- 数据源到底谁拥有
- 删除是乐观更新还是等待服务端确认
- 删除后导航状态怎么收口
四、跳转为什么容易和 List 纠缠出问题
很多人会把列表和跳转各自看成独立功能,但在真实项目里它们关系非常紧。
典型问题包括:
- 点进详情后删除当前项,返回列表状态不对
- 列表刷新后,原来的选中路径失效
- 搜索结果跳详情再返回,列表状态被重置
这些问题的本质通常不是 NavigationLink 不会写,而是:
- 列表数据状态
- 选中项状态
- 导航路径状态
没有被当成同一组问题处理。
五、性能问题为什么在 List 里特别容易爆
因为列表把很多小问题都会放大。
比如:
- item 视图层级太深
- 图片加载和解码太晚
- 文本和数据转换放在显示阶段做
- 刷新粒度过大
在普通页面里这些成本可能还能忍,但在 List 这种高频滚动场景里,很容易直接变成掉帧和闪动。
所以很多 List 性能问题,并不是 List 本身慢,而是它对数据身份、刷新范围和展示时机特别敏感。
六、一个更稳的思路:先把 List 当“状态投影”,而不是“装数据的容器”
很多人写列表时,脑子里默认是:
- 我有一组数据
- 放进 List
真实项目里更稳的思路是:
- 当前页面状态是什么
- 这组状态应该投影成哪一批列表项
- 哪些列表项有稳定身份
- 哪些用户动作会改变这组状态
一旦你把 List 当成状态投影,而不是简单容器,很多问题就会开始变清楚:
- 为什么要优先关注 identity
- 为什么删除、刷新、分页不能分开看
- 为什么跳转状态也要一起考虑
七、结论:List 的真正难点,是“身份、状态、交互”很容易一起缠住
如果只用一句话总结,我会说:
SwiftUI 里的
List真正难的地方,不是控件 API 本身,而是数据身份、页面状态和删除/刷新/跳转这些交互一旦缠在一起,任何一个边界没立住,问题都会一起暴露。
所以想把 List 用稳,关键不是会多少 modifier,而是先把:
- item 身份
- 列表状态
- 交互后状态流转
这三件事理顺。