驯服 Flutter 无限滚动:为何 async* 三行代码没抓住要点
来源:dev.to — 2026-09-03
📋 概述
Flutter 无限滚动的竞态几乎人人都踩过:用户手一甩就触发多次越过底部的滚动事件,首个异步请求还没回来监听器又触发,列表重复、页码跳变甚至状态机锁死。近期热文《3 Lines of Dart async Code That Fixed My Infinite Scroll Pagination》建议把分页逻辑封进 async 生成器并用 StreamIterator 消费。本文作者 Ali 的诊断虽准,却指出这套方案并未真正解决并发:StreamIterator.moveNext() 明确非并发安全,快速滚动时照样抛 Bad state,只是把手工守卫换成了流迭代器抽象。他主张并发应放在事件边界,用 BlocSignal 的无流事件变换器 droppable()/restartable() 一次到位。
🔑 核心要点
- StreamIterator.moveNext() 明确不支持并发——快滚或双重 rebuild 在上一轮 moveNext 还在等网络时再调用,直接抛 Bad state,作者也不得不保留手工 guard,所以问题只是换了个抽象
- 迭代器是命令式拉取,而真实 Flutter UI 需要响应式状态模型:第 4 页网络错误怎么办、如何保留已拉取项并渲染底部 spinner、如何做下拉刷新与空态,仍需外部容器来聚合
- StreamIterator 持活动订阅有资源泄漏风险:离开页面要记得 cancel(),改查询或筛选就得拆旧建新再重新绑定管道
- 并发属于事件边界:BlocSignal 用 streamless 事件变换器把并发做成头等公民,防重复请求只需一个参数 transformer: droppable()
- droppable() 用 isProcessing 布尔在同调用帧同步翻转,滚动手势在 pending 期间发来的每个事件都被安全丢弃,零竞态零微任务延迟零 Stream 分配
- 别维护独立 page 计数器——stateValue.posts.length 本身就是分页游标,从源头消除失步;改搜索查询用 restartable() 自动作废旧请求丢弃幽灵响应
💡 金句
把并发当事件边界的策略来处理,而不是埋进命令式数据拉取循环或泄漏到 UI 滚动监听里。
👍 0
👎 0
← 返回 dev.to 首页