
LazyForEach 的 key 我填错了列表刷新时闪得像电视雪花先上代码后面解释我为什么这么写。// 这是我一开始写的看起来没毛病对吧LazyForEach(this.dataSource,(item:Item,index:number){ListItem(){MyCard({item:item})}},(item:Item)JSON.stringify(item))// key 生成器问题就出在那个JSON.stringify(item)上。当时项目里有个消息列表数据量倒也不大就两百来条。需求加了个下拉刷新我一拉整个列表开始疯狂闪烁每一行都在重建复选框状态全丢了头像加载的占位图重新转圈圈。那场面跟老式电视机信号不好时的雪花屏差不多。我第一反应是刷新逻辑写错了。this.dataSource.reloadData(newList)是不是不应该全量替换是不是应该做增量 diff我跑去翻官方文档文档里只写了数据变化时触发 item 重建但没告诉我什么情况下会全量重建。排查了两天。第一天我怀疑是DataSource的实现问题重写了一遍IDataSource接口没解决。第二天我把notifyDataReload换成了notifyDataDeletenotifyDataAdd列表确实不闪了但性能更差因为删了再加等于走了两遍流程。你猜怎么着问题根本不在刷新方式上。我在 DevEco 的 Profiler 里抓了一帧发现每次刷新所有ListItem的aboutToAppear全被执行了一遍。两百个 item两百次重建。最后我把目光锁定了 key 生成器。JSON.stringify(item)的问题在于item 对象里有个timestamp字段每次刷新服务端都会返回新的时间戳。JSON.stringify一跑每个 key 都变了。鸿蒙的渲染引擎一看 key 全换了理所当然地认为是全新数据全部重建。说白了key 的作用就是告诉框架这条数据没变复用之前的组件。如果 key 变了框架就会销毁旧的、创建新的不管数据内容是不是真的不同。修复后的代码长这样LazyForEach(this.dataSource,(item:Item,index:number){ListItem(){MyCard({item:item})}},(item:Item)item.id)// 用业务唯一标识就这么一行改动。item.id是数据库主键刷新前后不会变。我重新编译下拉刷新列表稳稳的一个aboutToAppear都没多触发。顺手测了一组数据场景key 策略刷新耗时重建 item 数200 条消息列表JSON.stringify(item)约 420ms200200 条消息列表item.id约 35ms仅变更项500 条长列表JSON.stringify(item)直接卡死 1.5s500500 条长列表item.id约 60ms仅变更项差距是十倍起步。数据量大的时候JSON.stringify 本身就有序列化开销再加上全量重建不卡才怪。等一下这里我漏说一个前提。如果你用index当 key比如(item, index) index.toString()情况会更隐蔽。列表尾部追加数据时没问题但中间插入一条后面所有 index 都变了结果还是大面积重建。我见过有人用 index 当 key然后在列表顶部插入一条新消息整个列表闪了一下跟我的雪花屏异曲同工。正确的做法只有一个用业务层面的唯一标识。没有 id 的话自己根据数据内容构造一个稳定的 hash但千万别把会变的字段包进去。我还顺手写了个小工具函数项目里复用functionstableKey(item:Recordstring,any,fields:string[]):string{returnfields.map(fString(item[f]??)).join(|)}// 用法stableKey(item, [id, type])好处是强制你显式声明哪些字段参与 key 计算不会再手滑把timestamp之类的动态字段带进去。顺便说一句鸿蒙文档里关于 LazyForEach 的 key 说明藏得挺深的在高级组件章节的一个小注脚里我第一次读完全没注意到。要不是这次踩坑我估计到现在还觉得JSON.stringify是个偷懒的好办法。反正我以后不会再用 JSON.stringify 当 key 了。你要是也在用 LazyForEach现在就去检查一下你的 key 生成器。本文遵循 MIT 协议转载请注明出处。