懒加载的秘密:ArkTS 实现鸿蒙商品分页与总条数统计
实例商品分页列表Product技术LIMIT/OFFSET 分页、总条数统计、懒加载一、分页的三种方案对比在深入实现之前先建立分页方案的整体认知。数据库分页有三种主流方案方案SQL 形式优点缺点OFFSET 分页LIMIT 10 OFFSET 20实现简单任意跳页深分页性能差OFFSET 越大越慢游标分页WHERE id lastId LIMIT 10深分页性能稳定无法任意跳页需记录游标Keyset 分页WHERE (sort, id) (v, id) LIMIT 10复合排序下最优复杂本实例采用OFFSET 分页——因为它实现最简单limitAs offsetAs两个 API 搞定、支持任意跳页且 Demo 数据量60 条下 OFFSET 深分页的性能问题完全无感。方案选型看数据量几百条用 OFFSET 完全够百万级才需要考虑游标。读者理解三种方案的适用边界即可。二、OFFSET 分页的完整实现8-1 文章展示过queryPage与queryPageByCategory这里深入分页参数的语义staticasyncqueryPageByCategory(context:common.Context,limit:number,offset:number,category:string):PromiseGoods[]{conststoreawaitProductDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(ProductDao.TABLE);if(category!全部){predicates.equalTo(category,category);}predicates.orderByDesc(sales).limitAs(limit).offsetAs(offset);constresultawaitstore.query(predicates);returnProductDao.collect(result);}生成的 SQLSELECT*FROMgoodsWHEREcategory数码ORDERBYsalesDESCLIMIT10OFFSET20;语义拆解先按 category 过滤再按销量倒序然后跳过前 20 条OFFSET 20、取接下来 10 条LIMIT 10——即第 3 页。执行顺序WHERE → ORDER BY → LIMIT/OFFSETSQLite 按这个顺序处理理解它有助于排查分页边界问题。三、深分页的性能陷阱OFFSET 分页有一个众所周知的性能问题OFFSET 越大查询越慢。原因SQLite 处理LIMIT 10 OFFSET 20000时必须先扫描前 20010 条、跳过前 20000 条、只返回最后 10 条——前面 20000 条白白扫描。数据量小几百条无感但十万级数据翻到 10000 页时OFFSET 巨大每次查询都扫描全量性能灾难。生产级缓解方案理解即可本实例不实现// 游标分页记住上一页最后一条的 idstaticasyncqueryPageByCursor(context:common.Context,limit:number,lastId:number):PromiseGoods[]{constpredicatesnewrelationalStore.RdbPredicates(ProductDao.TABLE);if(lastId0){predicates.greaterThan(id,lastId);// 只取 id 上页末尾的}predicates.orderByAsc(id).limitAs(limit);// ...}WHERE id lastId LIMIT 10每次都从上次位置往后取不重复扫描——深分页性能恒定。代价是不能跳页只能下一页。本实例为什么用 OFFSET60 条数据分 6 页OFFSET 最大 50性能完全无感实现简洁2 个 API演示意图清晰教材要展示 LIMIT/OFFSET 语法本身。教材选最直白的方案生产按数据量升级。四、总条数统计与分页的配合头部「共 N 件商品」的 N 来自COUNT(*)staticasynccount(context:common.Context):Promisenumber{conststoreawaitProductDao.getStore(context);constresultawaitstore.querySql(SELECT COUNT(*) AS c FROM${ProductDao.TABLE});lettotal0;if(result.goToNextRow()){totalresult.getLong(result.getColumnIndex(c));}result.close();returntotal;}COUNT 与分页的配合COUNT 在首次加载时查询一次总量之后分页查询只关心「取哪一页」两者独立。页面用total显示总量、用goods.length显示已加载——「已加载 30 / 共 60 件」的进度感。COUNT 的性能COUNT(*)走全表扫描或索引扫描60 条毫秒级。生产环境百万级数据COUNT 也是开销点——常用方案是缓存总量定期刷新或用近似值。本实例不做优化理解即可。五、懒加载状态机页面层的核心懒加载的「翻页状态机」在 ProductPage 里是分页体验的灵魂。完整状态流转Stategoods:Goods[][];StatepageSize:number10;Statepage:number0;Stateloading:booleanfalse;Statefinished:booleanfalse;asyncloadMore():Promisevoid{if(this.loading||this.finished){return;// 状态守卫加载中/已完成则忽略}this.loadingtrue;constoffsetthis.page*this.pageSize;constlistawaitProductDao.queryPageByCategory(this.context,this.pageSize,offset,this.category);if(list.length0){this.goodsthis.goods.concat(list);this.page;}if(list.lengththis.pageSize){this.finishedtrue;}this.loadingfalse;}状态机四态状态触发行为初始页面加载page0, goods[], finishedfalse加载中loadMore 开始loadingtrue忽略重复触发追加返回 0 条concat page完成返回 pageSizefinishedtrue停止加载为什么list.length this.pageSize判完成如果请求 10 条但只返回 5 条说明数据库里只剩 5 条了——这就是最后一页。**「不足一页 没有更多」**是分页终止的经典判据比「list.length 0」更早触发恰好整除的情况不会多查一次空页。防重入的两种机制loading标志onReachEnd 连续触发时第一次已置 loadingtrue后续直接 returnfinished标志加载完成后不再触发。这两个守卫保证了「快速滚动到底」时不会重复请求同一页。六、分类切换与分页的重置切换分类时分页状态必须整体重置8-2 文章讲过这里深入原因asyncswitchCategory(cat:string):Promisevoid{this.categorycat;this.page0;// 页码归零this.goods[];// 列表清空this.finishedfalse;// 完成标志复位awaitthis.loadMore();// 从新分类的第一页开始}如果不重置会怎样从「数码」第 3 页切到「服饰」page 还是 2、goods 还是数码的 30 条——loadMore 会用 OFFSET20 去查服饰且列表尾部残留数码商品数据完全错乱。分类变化 新的分页序列所有分页状态必须重置。这是一个容易漏、漏了必出 bug 的细节。七、技术要点对照表技术点实现方式生产价值分页limitAs offsetAs一页一取排序索引idx_goods_salesORDER BY 走索引深分页OFFSET 大时慢 → 游标方案大数据量升级路径总量COUNT(*)头部进度信息终止判定list.length pageSize最后一页检测防重入loading finished 守卫懒加载正确性分类重置page/goods/finished 归零切换不错乱八、常见问题 FAQQ1page 从 0 开始还是从 1 开始A本实例从 0 开始page0→ OFFSET0 第一页。数组下标习惯 0 基页码习惯 1 基两者都可关键是页面内保持一致。OFFSET 计算公式page * pageSize在 0 基下最简洁。Q2切换分类后 total总条数需要更新吗A本实例的 total 是「全部商品数」COUNT 全表切换分类不变。如果产品要「每个分类的总数」需要按分类 COUNT——categoryCounts()的 GROUP BY 结果里已经有每类数量页面可以取categories[cat]显示分类总量。Q3加载失败怎么办A当前实现未处理 loadMore 的失败异常会冒泡到控制台。生产版应 try-catch失败时 loadingfalse 允许重试并 Toast 提示「加载失败」。懒加载的失败重试是生产必选项读者可自行补充。Q4为什么用 concat 而不是 pushAconcat返回新数组不可变更新push原地修改。ArkUI 的 State 响应式要求引用替换才能触发 UI 刷新——this.goods.concat(list)产生新数组赋值刷新生效this.goods.push(...)原地改不触发。这与 5-2 文章 Map 整体赋值的原理一致。Q5OFFSET 分页在删除数据后页码会乱吗A会。如果删了第 1 页的商品第 2 页的 OFFSET10 取到的内容会「前移错位」。本实例无删除功能商品只读无此问题。生产环境删除频繁时应考虑游标分页。Q6双列瀑布流的加载顺序和单列一样吗A一样。lanes 只是布局分列数据流仍是「数组顺序 → 分列渲染」——加载的 10 条依次填入两列。瀑布流的高度差异不影响加载逻辑。九、文章小结本篇文章深入讲解了商品分页的数据层与状态机limitAs/offsetAs 分页 idx_goods_sales 索引 COUNT 总量 懒加载四态状态机加载中/追加/完成/防重入。核心心法是「不足一页即完成」的终止判据和「分类切换必重置」的状态管理。OFFSET 分页的实现最简深分页的性能演进游标方案也做了铺垫——分页是数据量思维的第一课。下一篇8-4展示 60 条商品种子数据如何铺满三页验证分页与懒加载在真实数据下的效果。十、分页查询核心实现逐行解读前文展示了 loadMore 的整体结构这里把「页码 → OFFSET」的换算与「是否还有更多」的判断逐行拆解。先看最核心的两行constoffsetthis.page*this.pageSize;// ① 页码 × 每页条数 跳过条数constlistawaitProductDao.queryPageByCategory(this.context,this.pageSize,offset,this.category);// ② LIMIT 10 OFFSET 0/10/20…limit 与 offset 的换算表pageSize 10页码 pageoffset page × pageSize命中的记录页面语义00第 1~10 条第 1 页110第 11~20 条第 2 页220第 21~30 条第 3 页550第 51~60 条第 6 页最后页码状态 page 的三个关键点0 基计数page 从 0 开始offset 直接等于 page × pageSize公式最简若从 1 开始则要写(page - 1) * pageSize多一层减法先取后加loadMore 中「先用当前 page 算 offset 取数成功后才 page」——保证下一次触发时 page 已指向下一页顺序颠倒会重复取同一页内外解耦内部状态 0 基UI 展示用page 1如「第 3 页 / 共 6 页」显示层与状态层互不污染。「是否有更多」的判断逐行解读if(list.lengththis.pageSize){this.finishedtrue;// 请求 10 条返回不足 10 条 没有更多}返回 10 条 → 下一页还有货继续返回 5 条库中只剩 5 条→ 最后一页置 finished不再触发恰好整除的边界60 条最后一批恰好 10 条此时不置 finished会多触发一次 loadMore 查第 7 页返回 0 条——0 10成立同样置 finished。多查一次空页换代码极简60 条数据无感生产环境如在意可先 COUNT 再决定是否查。十一、排序切换综合/销量/价格 DESC 的谓词写法排序条三档「综合 / 销量 / 价格」本质只改 ORDER BY 子句。推荐用配置表映射排序串替代散落的 if-elseconstSORT_OPTIONS:Recordstring,string{综合:id ASC,// 默认按插入顺序销量:sales DESC,// 销量从高到低价格:price DESC, id ASC,// 价格降序同价按 id 兜底};predicates.orderBy(SORT_OPTIONS[this.sortKey]??id ASC).limitAs(this.pageSize).offsetAs(offset);三个细节orderBy()直接接收完整排序串比orderByDesc(sales)更灵活——切换排序时只换字符串谓词对象无需重建若当前代码用orderByDesc把排序条件提取成参数即可平滑升级价格排序必须带唯一键兜底多条商品同价时ORDER BY price DESC的返回顺序 SQLite 不保证稳定翻页时第 1 页末尾与第 2 页开头可能「重复或漏条」。追加, id ASC后同价商品按 id 定序分页才能不重不漏切换排序 新的结果序列必须走与分类切换相同的重置流程见十三节否则旧排序数据与新排序数据混列。生成的 SQLSELECT*FROMgoodsORDERBYpriceDESC,idASCLIMIT10OFFSET0;十二、总数 COUNT 与总页数计算第四节展示了 COUNT 查询总量这里补上「总页数」的推导——它是分页条显示「共 N 页」的依据consttotalPagesMath.ceil(this.total/this.pageSize);// 向上取整总条数 totalpageSizetotalPages最后一页条数6010610满页551065不足一页2110310100—为什么要向上取整55 ÷ 10 5.5第 6 页只有 5 条——只要有余数就得多一页Math.ceil精确表达「最后不满一页也算一页」。它与懒加载的list.length pageSize判断互为印证totalPages 回答「最多有几页」finished 回答「实际到没到最后一页」两者数值对得上说明分页状态一致。实际使用中 totalPages 还可用于「下一页按钮置灰」当前页 ≥ totalPages 时禁用是分页体验的收尾细节。十三、下拉刷新重置分页逻辑下拉刷新Refresh 组件的 onRefresh与分类切换本质相同——刷新 从第一页重新开始asynconRefresh():Promisevoid{this.page0;// ① 页码回到第一页this.goods[];// ② 旧列表清空this.finishedfalse;// ③ 完成标志复位this.totalawaitProductDao.count(this.context);// ④ 总量一并刷新awaitthis.loadMore();// ⑤ 重新加载第一页}为什么三步重置缺一不可只清 goods、不归零 pageloadMore 会用旧 OFFSET如 20取「第 3 页」拼到空列表上列表直接缺前两页不复位 finishedloadMore 一进来就被状态守卫if (this.finished) return拦下刷新完全失效不刷新 total删除/新增商品后头部总数停留在旧值。与其他入口的统一分类切换、排序切换、下拉刷新共用同一套「归零 → 重载」模板只是触发源不同——把三处逻辑收敛为一个resetAndLoad()方法可消除重复代码asyncresetAndLoad():Promisevoid{this.page0;this.goods[];this.finishedfalse;awaitthis.loadMore();}刷新完成后收起刷新动画并提示「已刷新」懒加载从新序列第一页继续。十四、分页细节 FAQQ1切换排序后必须重置分页吗A必须。排序改变整个结果序列「第 3 页」不再是同一批数据。与分类切换同理page 归零 goods 清空 finished 复位再按新排序加载第一页漏了会出现「综合排序的旧数据 价格排序的新数据」混杂。Q2为什么价格排序要带id ASC兜底ASQL 规范不保证 ORDER BY 相等值的返回顺序稳定。同价商品两次查询顺序可能不同翻页会重复或漏条。追加唯一键后顺序完全确定——ORDER BY 带唯一键兜底是分页排序的通用最佳实践。Q3totalPages 在 total0 时是 0UI 怎么处理A头部显示「共 0 件商品」分页条隐藏或显示「暂无数据」懒加载第一次 loadMore 因0 pageSize置 finished不会死循环请求。Q4下拉刷新与触底加载同时触发会冲突吗A不会。两者共用 loading 守卫刷新开始先置 loadingtrue触底事件被拦刷新结束 loadingfalse触底恢复。所有加载入口共用一把锁状态机才不乱。Q5OFFSET 与 LIMIT 可以省略吗A可以。LIMIT单独使用表示「最多返回 N 条」如排行榜 Top10OFFSET单独使用不合法需配合 LIMIT。分页必须成对出现顺序固定LIMIT n OFFSET m。

相关新闻

关于画图工具的优化

关于画图工具的优化

关于画图工具的优化 针对于之前的画图的优化有以下几点 1曲线的增加 2 对于橡皮擦的优化 3 关于粗细的调整 4对于移动窗体画图的消失 1曲线的增加 主要用到了新的监听器MouseMotionListener 他有俩个方法分别为mouseDragged和mouseMove 主要用到了mouseDragged具体如下 1&#…

2026/9/25 16:49:25 阅读更多 →
50元预算实现专业级AI音频降噪方案

50元预算实现专业级AI音频降噪方案

1. 项目概述:低成本AI降噪方案的价值与定位作为一名长期关注音频处理技术的从业者,我深刻理解学生群体在专业学习中对降噪工具的需求与预算限制之间的矛盾。市面上主流AI降噪软件如Adobe Audition、iZotope RX动辄上千元的年费订阅,而专业录音…

2026/9/24 0:26:55 阅读更多 →
中国供应链EMBA横评:复旦科创生态如何赋能东南亚

中国供应链EMBA横评:复旦科创生态如何赋能东南亚

引言 东南亚华裔企业家希望链接中国产业链,泰国高管希望理解中国市场,两类申请人的共同需求是进入稳定的华语商业环境,并获得课程、校友与产业场景的持续支持。中文沟通只是起点,项目能否连接目标行业、长三角供应链、科创资源和亚…

2026/9/23 5:08:42 阅读更多 →

最新新闻

SpringBoot+Vue 实现办公用品管理系统|计算机毕设源码讲解

SpringBoot+Vue 实现办公用品管理系统|计算机毕设源码讲解

💖💖作者:计算机毕业设计小明哥 💙💙个人简介:曾长期从事计算机专业培训教学,本人也热爱上课教学,语言擅长Java、微信小程序、Python、Golang、安卓Android等,开发项目包…

2026/9/25 22:07:44 阅读更多 →
Python Assert 语句

Python Assert 语句

我们要去搞明白, 到底什么叫做断言。断言是程序里用来坚定地声明或表明某个事实的语句。比如在编一个除法的函数时, 你内心非常确定, 那个除数是不应该等于零的, 所以你就发出了断言, 说明这个除数不是零。断言仅仅只是一个布尔表达式, 它的作用是用来检查某个具体的条件有没有…

2026/9/25 22:07:44 阅读更多 →
阿里云 300万美金加入 Linux 基金会 Alibaba Cloud joins as a Founding Corporate Patron with $3 million

阿里云 300万美金加入 Linux 基金会 Alibaba Cloud joins as a Founding Corporate Patron with $3 million

阿里巴巴云正式加入 Omacom 基金会,成为创始企业赞助人,承诺每年出资 100 万美元,连续三年!这意味着总计 300 万美元的投入,与 DigitalOcean 的赞助金额持平,将全部用于 Omarchy 的开发、维护与推广。 但这…

2026/9/25 22:06:44 阅读更多 →
云服务器怎么搭建python环境变量管理系统

云服务器怎么搭建python环境变量管理系统

要搭建一个系统用来管理环境变量这事儿, 它并不是简简单单就能弄好的, 你首先得具备一定的基础知识储备, 并且还要有一定的编程实际操作经验才行;接下来这儿有一个非常基础的系统框架可以摆在你的面前供你看一看, 这个框架可不是固定不变的死规矩, 它是可以根据你自…

2026/9/25 22:06:44 阅读更多 →
提示词实测:剩菜太多不知道吃什么,让 AI 直接决定今晚菜单

提示词实测:剩菜太多不知道吃什么,让 AI 直接决定今晚菜单

冰箱里剩下一堆食材、又不想专门买菜时,晚上吃什么最头疼。我实测了一组提示词,把人数、食材、口味和时间限制一次性告诉 AI,让它直接决定菜单,而不是列一堆菜让我自己选。提示词的关键要求 提示词要求 AI 优先使用现有食材、根据…

2026/9/25 22:05:43 阅读更多 →
init_rootfs / shmem_init / init_ramfs_fs 函数

init_rootfs / shmem_init / init_ramfs_fs 函数

init_rootfs1. init_rootfs 函数1.1 shmem_init 函数1.2 init_ramfs_fs 函数1. init_rootfs 函数 通过 register_filesystem 函数,将新的rootfs文件系统插入到全局链表file_systems中 通过 init_ramfs_fs()->register_filesystem 函数,将一个新的ram…

2026/9/25 22:05:43 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/25 19:27:14 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/25 11:15:26 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/25 20:29:09 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/25 20:29:43 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/25 20:29:31 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/25 19:27:26 阅读更多 →