大家好,我是子玥酱,一名长期深耕在一线的前端程序媛 👩💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。
我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括前端工程化、小程序、React / RN、Flutter、跨端方案,
在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。
技术方向:前端 / 跨端 / 小程序 / 移动端工程化
内容平台:掘金、知乎、CSDN、简书
创作特点:实战导向、源码拆解、少空谈多落地
文章状态:长期稳定更新,大量原创输出
我的内容主要围绕前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读展开。文章不会停留在“API 怎么用”,而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。
子玥酱 · 前端成长记录官 ✨
👋 如果你正在做前端,或准备长期走前端这条路
📚 关注我,第一时间获取前端行业趋势与实践总结
🎁 可领取11 类前端进阶学习资源(工程化 / 框架 / 跨端 / 面试 / 架构)
💡 一起把技术学“明白”,也用“到位”
持续写作,持续进阶。
愿我们都能在代码和生活里,走得更稳一点 🌱
文章目录
- Flutter 从来不是“按行 rebuild”
- 一次列表滚动,Flutter 实际在做什么
- 滚动时 Flutter 的真实流程
- rebuild 的“真正触发条件”
- setState
- InheritedWidget 依赖变化
- 父 Widget rebuild
- 一个“看似安全,实际危险”的列表写法
- Flutter 列表的“安全边界”到底在哪里
- 正确拆边界的基本写法
- 列表容器不订阅业务状态
- item 自己订阅自己的数据
- rebuild、layout、paint,不是一回事
- rebuild ≠ layout ≠ paint
- 为什么你“感觉整个列表都在 rebuild”
- 一个更接近真实项目的 Demo 对比
- 错误边界写法
- 正确边界写法
- Flutter 为什么不替你“自动做对”
- 一句话总结 rebuild 的真正边界
- 总结
很多 Flutter 列表性能问题,本质都可以归结成一句话:
你以为只 rebuild 了一行,其实 rebuild 了一片。
Flutter 不慢,慢的是你没搞清楚它以什么为边界做更新。
Flutter 从来不是“按行 rebuild”
这是一个非常重要、但经常被误解的点。
在 Flutter 里:
- rebuild 的最小单位不是 ListView 的 item
- 而是:Widget 子树
也就是说:
rebuild 到哪一层停,完全取决于你把状态“挂”在了哪一层。
一次列表滚动,Flutter 实际在做什么
我们从一次最普通的滚动开始拆。
滚动时 Flutter 的真实流程
滚动发生
Sliver 判断哪些 item 进入 / 离开可视区
对新进入的 item:
- 调用 itemBuilder
- build Widget
- layout
- paint
对离开的 item:
- Element 可能被回收或缓存
注意这里的关键词:itemBuilder 会被重新调用
但这并不等于:
item 一定 rebuild
更不等于整个列表 rebuild
rebuild 的“真正触发条件”
Flutter rebuild 的触发只有几类,非常明确:
setState
setState((){count++;});- 会触发当前 State 对应 Widget 子树 rebuild
- 不会自动波及兄弟节点
- 更不会影响父节点
InheritedWidget 依赖变化
finalmodel=context.watch<MyModel>();- 当前 widget 订阅了 model
- model notifyListeners
- 当前 widget 及其子树 rebuild
这里是列表最容易“炸”的地方。
父 Widget rebuild
这是最容易被忽略、但影响最大的一类。
@overrideWidgetbuild(BuildContextcontext){returnColumn(children:[Header(),ListView.builder(...),],);}如果Columnrebuild:
ListView.builder 会重新执行 build
但注意:
- 不等于所有 item 都重新 layout
- 但 itemBuilder 会被重新调用
一个“看似安全,实际危险”的列表写法
classListPageextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){finalmodel=context.watch<ListModel>();returnListView.builder(itemCount:model.items.length,itemBuilder:(context,index){returnListTile(title:Text(model.items[index].title),);},);}}表面看起来:
- ListView 是 builder
- item 是轻量的
但这里的 rebuild 边界是:
整个 ListPage
只要model.notifyListeners():
- ListPage rebuild
- ListView rebuild
- itemBuilder 被重新执行
在 Debug 下,你会明显感觉到卡顿。
Flutter 列表的“安全边界”到底在哪里
真正合理的 rebuild 边界,应该满足三件事:
- 列表容器本身尽量稳定
- item 自己决定要不要 rebuild
- 状态变化只影响必要的子树
正确拆边界的基本写法
列表容器不订阅业务状态
classListPageextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnListView.builder(itemCount:context.select<ListModel,int>((m)=>m.items.length,),itemBuilder:(context,index){returnItemTile(index:index);},);}}这里有两个关键点:
- ListPage 不 watch 整个 model
- 只关心列表长度
item 自己订阅自己的数据
classItemTileextendsStatelessWidget{finalint index;constItemTile({requiredthis.index});@overrideWidgetbuild(BuildContextcontext){finalitem=context.select<ListModel,Item>((m)=>m.items[index],);returnListTile(title:Text(item.title),);}}这样 rebuild 边界就变成了:
单个 item
rebuild、layout、paint,不是一回事
这里必须澄清一个非常常见的误解。
rebuild ≠ layout ≠ paint
- rebuild:重新生成 Widget
- layout:重新计算大小和位置
- paint:重新绘制像素
Flutter 在很多情况下:
- 会 rebuild
- 但不会 layout
- 更不会 repaint
而列表性能问题,往往出在 rebuild 范围过大。
为什么你“感觉整个列表都在 rebuild”
因为 Flutter 没有替你做“业务级别”的判断。
它只知道:
- 哪个 widget 依赖了什么
- 哪个 widget 变了
它不知道:
“你只是改了一行数据”
所以如果你写的是:
context.watch<ListModel>();Flutter 的理解是:
“这个 widget 依赖整个 ListModel”
一个更接近真实项目的 Demo 对比
错误边界写法
ChangeNotifierProvider(create:(_)=>ListModel(),child:ListPage(),);classListPageextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){finalmodel=context.watch<ListModel>();returnListView.builder(itemCount:model.items.length,itemBuilder:...);}}任何一条 item 更新,整个列表参与 rebuild。
正确边界写法
classListPageextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){finalcount=context.select<ListModel,int>((m)=>m.items.length,);returnListView.builder(itemCount:count,itemBuilder:(context,index){returnItemTile(index:index);},);}}更新单个 item,只影响对应的 ItemTile。
Flutter 为什么不替你“自动做对”
这是一个设计取舍,而不是能力不足。
Flutter 的哲学是:
- 框架只保证机制正确
- 不猜测你的业务意图
- 不偷偷帮你缓存或合并更新
代价就是:
你必须自己划清 rebuild 的边界
一句话总结 rebuild 的真正边界
如果用一句话概括 Flutter 列表 rebuild 的真相:
rebuild 到哪里停,不是 ListView 决定的,而是你把状态放在哪一层决定的。
理解这一点之后:
- Debug 下的卡顿
- 大列表掉帧
- Provider / Riverpod 的性能差异
都会变成可解释、可控制的问题。
总结
Flutter 列表性能的天花板,从来不在框架。
它在于:
- 你是否明确 rebuild 边界
- 你是否尊重 Widget 子树的更新模型
- 你是否愿意为“结构清晰”多写一点代码