第 6 章 · 指令与列表 — vfor / List / PageList / 复用机制

滚动右边「List 模拟器」,看 Item 如何被回收复用 · 切换状态机 demo 体验条件渲染

条件渲染 vs 列表渲染

需求语法例子
条件渲染(vif 风格)if / when / ?.letif (loggedIn) Text { } else Button { }
短列表(<20 条)forEachcities.forEach { Text { ... } }
长列表 / 滚动List + vforList { vfor({ items }) { ... } }
整页翻动PageList + vfor抖音上下滑、轮播图
瀑布流WaterfallList + vfor小红书首页、Pinterest

forEach vs vfor 对比

维度forEachvfor
调用时机attr 块执行时(一次)List 滚动时按需
创建节点全部仅可视区附近
复用✅ 滑出回收,滑入复用
内存跟数据量线性跟可视区相关
用在哪普通容器(View / Scroller)List / PageList / WaterfallList

关键事实:forEach 1000 条 = 卡死

// ❌ 1000 条数据用 Scroller + forEach
Scroller {
    items.forEach { Text { ... } }    // 创建 1000 个 View → 内存 ~50MB+,首屏卡 1-2s
}

// ✅ 用 List + vfor
List {
    vfor({ items }) { Text { ... } }  // 实际只创建 ~12 个 View → 内存 ~5MB

List 复用机制 — 可视化演示

下面两个"模拟器"都展示同一份 100 条数据。左边模拟 Scroller(无复用),右边模拟 List(有复用)。 右上角统计:实际创建的 View 数。滚动右边的 List 模拟器,注意"已复用"次数会增加

❌ Scroller + forEach(无复用)

View 实例数: 100
内存估算: ~5 MB

✅ List + vfor(有复用)

View 实例数: 5
已复用次数: 0

原理图解

屏幕高度 200px,每个 item 50px → 一屏 4 个 item

List 实际策略(橙色 = 被复用的 View):
┌─────────────────────────────────────────────┐
│ View1 ← 显示 item[0] (绿色:原创建)           │
│ View2 ← 显示 item[1]                         │
│ View3 ← 显示 item[2]                         │
│ View4 ← 显示 item[3]                         │
│ View5 ← 缓冲区 (即将进入)                    │
└─────────────────────────────────────────────┘

向下滚 → item[0] 滑出 → View1 进入回收池 →
显示 item[5]:从池中取 View1,更新数据为 item[5] (橙色:复用)

最终:View 实例总数永远 ≈ 一屏数 + 缓冲区

条件渲染:状态机 demo

常见的「加载中 / 空数据 / 失败 / 成功」4 态切换,用 when 一气呵成。

加载中…

对应代码

when (ctx.status) {
    "loading" -> View { Text { attr { text("⏳ 加载中…") } } }
    "empty"   -> View { Text { attr { text("📭 暂无数据") } } }
    "error"   -> View {
        Text { attr { text("加载失败"); color(Color.RED) } }
        View {
            event { click { ctx.retry() } }
            Text { attr { text("点击重试") } }
        }
    }
    "success" -> List { vfor({ ctx.items }) { ... } }
}

消息列表 · 实战 demo

List + vfor + 头像 + 未读红点的标准实现。点击消息行模拟跳转。

消息

关键代码

List {
    attr { flex(1f); paddingTop(8f) }
    event {
        scrollEnd { e ->
            if (atBottom(e) && !ctx.loadingMore) ctx.loadMore()
        }
    }

    Refresh {
        attr { refreshing(ctx.refreshing) }
        event { refresh { ctx.refresh() } }
    }

    vfor({ ctx.messages }) { msg ->
        renderMessageRow(msg).invoke(this)
    }

    if (ctx.loadingMore) {
        View { Text { attr { text("加载更多…") } } }
    }
}

瀑布流 · 小红书风格 2 列

WaterfallList + 高度可变的 cover 图 → 经典瀑布效果。

关键代码

WaterfallList {
    attr {
        flex(1f)
        columnCount(2)         // 2 列
        columnSpacing(8f)
        rowSpacing(8f)
    }

    vfor({ ctx.notes }) { note ->
        View {
            attr { backgroundColor(Color.WHITE); borderRadius(8f) }
            Image {
                attr {
                    src(note.coverUrl)
                    width(100f)
                    height(note.coverHeight)   // ★ 关键:高度可变
                }
            }
            Text { attr { text(note.title) } }
        }
    }
}