避免嵌套使用 ScrollView 组件

了解如何避免嵌套使用 ScrollView 导致的懒加载无法工作的问题。

避免嵌套使用 ScrollView 组件

在开发 Filmo 的过程中,在使用 LazyVStack 或 LazyVGrid 加载远程图片时,我遇到一个性能相关的问题:所有图片在视图出现的瞬间就全部开始加载,而不是预期中的随着滚动逐步加载。这个问题会导致:

  • 应用内存占用突然飙升
  • 用户界面出现明显卡顿
  • 网络请求瞬间激增

原因分析

经过排查,这个问题通常是由嵌套使用 ScrollView 组件导致的。当出现嵌套 ScrollView 时:

  1. 外层 ScrollView 会尝试计算其内容的总高度
  2. 这个计算过程会导致它初始化内部的所有子视图
  3. 内层视图中的 LazyVGrid/LazyVStack 被强制提前初始化
  4. 即使使用了 Lazy 组件,其懒加载效果也被外层 ScrollView 的预加载行为覆盖
  5. 最终结果是所有图片同时请求加载,而非随着滚动逐步加载

ScrollView 在 TabView 应用中的最佳实践

对于具有多个 Tab 的应用,推荐将 ScrollView 放在每个 Tab 的主页面中,而不是更深层的子组件中。

  • 清晰的视图层次结构:每个 Tab 只有一个主要滚动区域,避免嵌套
  • 更好的性能:减少内存使用,因为只有当前选中的 Tab 会初始化其 ScrollView
  • 统一的滚动体验:整个 Tab 内容有一致的滚动行为
  • 简化下拉刷新实现:在 Tab 级别实现 .refreshable 更直观

推荐的代码结构:

TabView {
    // 第一个标签页
    NavigationStack {
        ScrollView {
            VStack {
                HeaderView()
                ContentListView() // 不包含自己的 ScrollView
                FooterView()
            }
        }
        .refreshable { /* 刷新逻辑 */ }
    }
    .tabItem { /* 标签配置 */ }
    
    // 第二个标签页
    NavigationStack {
        ScrollView {
            OtherTabContent() // 不包含自己的 ScrollView
        }
    }
    .tabItem { /* 标签配置 */ }
}