在上一篇《HarmonyOS 教学实战(二)》中,我们已经让应用具备了:
-
网络请求
-
本地缓存
-
离线兜底
-
完整 Store 设计
但只要你的数据一多,列表就一定是性能重灾区。
真实项目中,80% 的卡顿都来自:
❌ 列表一次性加载
❌ 下拉刷新逻辑混乱
❌ 滚动时频繁重建组件
这一篇,我们专门解决这些问题。
一、这一篇我们要解决哪些真实痛点?
围绕一个真实列表页面,完成 3 件事:
-
📄 列表分页加载(上拉加载更多)
-
🔄 下拉刷新(重新请求第一页)
-
🚀 列表性能优化(可复用组件 + 状态收敛)
完成后,你的列表会具备:
数据多也不卡、刷新快、滚动顺
二、先改接口:支持分页返回
假设后端接口升级为:
GET /api/tasks?page=1&pageSize=10
返回格式:
{
"list": [
{ "id": 101, "title": "学习 HarmonyOS" }
],
"hasMore": true
}
三、第一步:升级网络 Service(分页支持)
修改 service/TaskService.ets
export interface PageResult<T> {
list: T[]
hasMore: boolean
}
export class TaskService {
static async fetchTasks(
page: number,
pageSize: number
): Promise<PageResult<Task>> {
const httpRequest = http.createHttp()
const response = await httpRequest.request(
`https://example.com/api/tasks?page=${page}&pageSize=${pageSize}`,
{ method: http.RequestMethod.GET }
)
const data = JSON.parse(response.result as string)
return {
list: data.list.map(item => new Task(item.id, item.title)),
hasMore: data.hasMore
}
}
}
四、第二步:升级 Store(分页核心逻辑)
分页的核心逻辑必须放在 Store,而不是 UI。
修改 model/TaskModel.ets
@ObservedV2
export class TaskStore {
tasks: Task[] = []
page: number = 1
pageSize: number = 10
hasMore: boolean = true
loading: boolean = false
refreshing: boolean = false
async refresh() {
if (this.refreshing) return
this.refreshing = true
this.page = 1
const result = await TaskService.fetchTasks(this.page, this.pageSize)
this.tasks = result.list
this.hasMore = result.hasMore
this.refreshing = false
}
async loadMore() {
if (!this.hasMore || this.loading) return
this.loading = true
this.page++
const result = await TaskService.fetchTasks(this.page, this.pageSize)
this.tasks = this.tasks.concat(result.list)
this.hasMore = result.hasMore
this.loading = false
}
}
教学重点
-
refresh():只管第一页 -
loadMore():只管追加 -
UI 只需要调用,不参与逻辑判断
五、第三步:页面中接入下拉刷新 & 上拉加载
修改 pages/Index.ets
@ComponentV2
struct Index {
@Local store = new TaskStore()
@Once
async init() {
await this.store.refresh()
}
build() {
Column() {
Refresh({ refreshing: this.store.refreshing }) {
List() {
ForEach(this.store.tasks, (item: Task) => {
TaskItem({ task: item, onDelete: () => {} })
}, item => item.id.toString())
if (this.store.hasMore) {
ListItem() {
Text(this.store.loading ? '加载中...' : '上拉加载更多')
.onAppear(() => this.store.loadMore())
}
}
}
}
}
}
}
📌 onAppear 是分页加载的关键
📌 不需要监听滚动位置
📌 非常稳定
六、第四步:列表性能优化(重点)
1️⃣ 使用 @ReusableV2 优化列表项
@ReusableV2
@ComponentV2
export struct TaskItem {
@Param task: Task
@Event onDelete: (id: number) => void
build() {
Row() {
Text(this.task.title)
Button("删除")
.onClick(() => this.onDelete(this.task.id))
}
}
}
📌 ArkUI 会自动复用组件结构
📌 滚动时不会频繁销毁/重建
2️⃣ 避免 ListItem 中使用 @Local 状态
❌ 错误示例:
@Local checked = false
会导致每个 Item 都维护自己的状态,极易卡顿。
✔ 正确做法:
状态上移到 Store 或 Model
3️⃣ 控制刷新粒度(状态收敛)
避免这样的写法:
@Local store = new TaskStore() // 整个 store 变 → 全列表刷新
更优方式是:
-
列表只依赖
tasks -
loading 状态放在 footer
七、第五步:缓存 + 分页怎么配合?
推荐策略:
| 场景 | 行为 |
|---|---|
| 首次进入 | 读缓存 → 再 refresh |
| 下拉刷新 | 丢弃缓存 |
| 上拉加载 | 不写缓存 |
| 退出页面 | 保存当前列表 |
📌 不要每一页都缓存
📌 缓存的是"可用数据",不是"中间态"
八、常见分页性能坑(必看)
❌ 坑 1:List + Column 嵌套
会导致全部子项提前创建
✔ 用 List + ListItem
❌ 坑 2:key 不稳定
key = index // 非常危险
✔ 使用业务 id
❌ 坑 3:刷新 & 加载同时进行
✔ Store 中用标志位严格限制
九、现在你的 App 已经是"工业级列表"了
你已经具备:
✔ 分页加载
✔ 下拉刷新
✔ 状态隔离
✔ 高性能列表
✔ 可扩展 Store 结构
这已经是 90% 商业 App 列表的实现水平。