返回文章列表

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 身份
  • 列表状态
  • 交互后状态流转

这三件事理顺。