及时做APP开发实战(十二)-LazyForEach懒加载优化实践

📅 2026/7/19 2:53:51
及时做APP开发实战(十二)-LazyForEach懒加载优化实践
及时做APP开发实战(十二)-LazyForEach懒加载优化实践本文将详细介绍如何使用LazyForEach实现长列表懒加载提升应用渲染性能。一、长列表性能问题1.1 问题分析【图1ForEach vs LazyForEach】ForEach渲染方式 ┌─────────────────────────────────────┐ │ 数据: [item1, item2, ..., item100] │ │ ↓ │ │ 一次性创建100个组件 │ │ ↓ │ │ ❌ 首屏渲染慢 │ │ ❌ 内存占用高 │ │ ❌ 滑动卡顿 │ └─────────────────────────────────────┘ LazyForEach渲染方式 ┌─────────────────────────────────────┐ │ 可视区域: 显示5个item │ │ ↓ │ │ 只创建5个组件 缓存 │ │ ↓ │ │ ✅ 首屏渲染快 │ │ ✅ 内存占用低 │ │ ✅ 滑动流畅 │ └─────────────────────────────────────┘1.2 适用场景场景数据量推荐方式少量数据 20条ForEach中等数据20-100条ForEach或LazyForEach大量数据 100条LazyForEach二、LazyForEach基础2.1 基本语法LazyForEach(dataSource:IDataSource,// 数据源itemGenerator:(item:T,index:number)void,// item生成函数keyGenerator:(item:T,index:number)string// key生成函数)2.2 IDataSource接口interfaceIDataSource{// 获取数据总数totalCount():number;// 获取指定索引的数据getData(index:number):T;// 注册数据变化监听器registerDataChangeListener(listener:DataChangeListener):void;// 注销数据变化监听器unregisterDataChangeListener(listener:DataChangeListener):void;}2.3 DataChangeListener接口interfaceDataChangeListener{// 数据重新加载onDataReloaded():void;// 数据添加onDataAdded(index:number):void;// 数据删除onDataDeleted(index:number):void;// 数据变化onDataChanged(index:number):void;}三、数据源实现3.1 TodoListDataSource实现import{TodoItem}from../model/TodoItem;exportclassTodoListDataSourceimplementsIDataSource{privatetodoList:TodoItem[][];privatelisteners:DataChangeListener[][];// 获取数据总数totalCount():number{returnthis.todoList.length;}// 获取指定索引的数据getData(index:number):TodoItem{returnthis.todoList[index];}// 注册监听器registerDataChangeListener(listener:DataChangeListener):void{if(this.listeners.indexOf(listener)0){this.listeners.push(listener);}}// 注销监听器unregisterDataChangeListener(listener:DataChangeListener):void{constposthis.listeners.indexOf(listener);if(pos0){this.listeners.splice(pos,1);}}// 更新整个列表updateData(newList:TodoItem[]):void{this.todoListnewList;this.notifyDataReload();}// 添加数据addData(todo:TodoItem):void{this.todoList.push(todo);this.notifyDataAdd(this.todoList.length-1);}// 删除数据deleteData(index:number):void{this.todoList.splice(index,1);this.notifyDataDelete(index);}// 通知数据重新加载privatenotifyDataReload():void{this.listeners.forEach(listener{listener.onDataReloaded();});}// 其他通知方法...}3.2 数据源类结构【图2数据源类结构】┌─────────────────────────────────────┐ │ TodoListDataSource │ ├─────────────────────────────────────┤ │ - todoList: TodoItem[] │ │ - listeners: DataChangeListener[] │ ├─────────────────────────────────────┤ │ totalCount(): number │ │ getData(index): TodoItem │ │ registerDataChangeListener() │ │ unregisterDataChangeListener() │ │ updateData(newList): void │ │ addData(todo): void │ │ deleteData(index): void │ │ - notifyDataReload(): void │ │ - notifyDataAdd(index): void │ │ - notifyDataDelete(index): void │ └─────────────────────────────────────┘四、页面中使用LazyForEach4.1 基本使用import{TodoListDataSource}from../utils/TodoListDataSource;import{TodoItem}from../model/TodoItem;EntryComponentstruct TodoListPage{// 数据源privatetodoDataSource:TodoListDataSourcenewTodoListDataSource();aboutToAppear(){// 初始化数据consttodosthis.loadTodos();this.todoDataSource.updateData(todos);}build(){Column(){List({space:10}){LazyForEach(this.todoDataSource,(item:TodoItem,index:number){ListItem(){this.TodoItemBuilder(item,index)}},(item:TodoItem)item.id.toString()// key生成函数)}.width(100%).height(100%)}}BuilderTodoItemBuilder(item:TodoItem,index:number){Row(){Text(item.text).fontSize(16).fontColor(item.completed?#999:#333)if(item.completed){Text(✓).fontSize(20).fontColor(#4CAF50)}}.width(100%).height(60).backgroundColor(#FFFFFF).borderRadius(8)}}4.2 渲染流程【图3LazyForEach渲染流程】┌─────────────────────────────────────┐ │ LazyForEach渲染流程 │ ├─────────────────────────────────────┤ │ 1. 计算可视区域 │ │ ↓ │ │ 2. 确定需要渲染的索引范围 │ │ ↓ │ │ 3. 调用dataSource.getData(index) │ │ ↓ │ │ 4. 调用itemGenerator创建组件 │ │ ↓ │ │ 5. 缓存已创建的组件 │ │ ↓ │ │ 6. 滑动时复用/创建组件 │ └─────────────────────────────────────┘五、数据更新处理5.1 添加待办// 添加待办asyncaddTodo(text:string){consttodocreateTodoItem(text);// 方法一更新整个列表constnewList[...this.todoList,todo];this.todoDataSource.updateData(newList);// 方法二增量更新更高效this.todoDataSource.addData(todo);}5.2 删除待办// 删除待办asyncdeleteTodo(index:number){// 方法一更新整个列表constnewListthis.todoList.filter((_,i)i!index);this.todoDataSource.updateData(newList);// 方法二增量删除this.todoDataSource.deleteData(index);}5.3 更新待办// 切换完成状态asynctoggleComplete(index:number){constitemthis.todoDataSource.getData(index);constnewItem:TodoItem{...item,completed:!item.completed};// 更新数据源constnewListthis.todoList.map((todo,i)iindex?newItem:todo);this.todoDataSource.updateData(newList);}六、性能对比6.1 渲染性能测试【图4性能对比】数据量ForEach首屏LazyForEach首屏提升100条320ms45ms7x500条1580ms52ms30x1000条3200ms55ms58x6.2 内存占用对比数据量ForEach内存LazyForEach内存节省100条12MB3MB75%500条58MB4MB93%1000条115MB5MB96%七、最佳实践7.1 使用建议【图5LazyForEach使用建议】┌─────────────────────────────────────┐ │ ✅ 使用建议 │ ├─────────────────────────────────────┤ │ □ 数据量100条时使用LazyForEach │ │ □ 实现完整的IDataSource接口 │ │ □ 使用唯一且稳定的key │ │ □ 优先使用增量更新而非全量更新 │ │ □ 配合List组件使用 │ └─────────────────────────────────────┘7.2 Key生成规范// ✅ 推荐使用唯一ID(item:TodoItem)item.id.toString()// ❌ 不推荐使用index作为key(item:TodoItem,index:number)index.toString()// 原因数据变化时index会改变导致组件重建7.3 常见问题问题1数据更新后UI不刷新// ❌ 错误直接修改数据源内部数组this.todoDataSource.todoList.push(newItem);// ✅ 正确通过数据源方法更新this.todoDataSource.addData(newItem);问题2滑动时组件闪烁// 原因key不稳定// 解决使用唯一且不变的key(item:TodoItem)item.id.toString()八、总结本文介绍了LazyForEach懒加载的实现和优化数据源实现实现IDataSource和DataChangeListener接口页面集成在List组件中使用LazyForEach数据更新通过数据源方法触发UI更新性能提升大幅减少首屏渲染时间和内存占用LazyForEach是处理长列表的最佳选择合理使用可以显著提升应用性能。