

子玥酱 (掘金 / 知乎 / CSDN / 简书 同名)
大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。
我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括 前端工程化、小程序、React / RN、Flutter、跨端方案,
在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。
技术方向: 前端 / 跨端 / 小程序 / 移动端工程化 内容平台: 掘金、知乎、CSDN、简书 创作特点: 实战导向、源码拆解、少空谈多落地 **文章状态:**长期稳定更新,大量原创输出
我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在"API 怎么用",而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。
子玥酱 · 前端成长记录官 ✨
👋 如果你正在做前端,或准备长期走前端这条路
📚 关注我,第一时间获取前端行业趋势与实践总结
🎁 可领取 11 类前端进阶学习资源 (工程化 / 框架 / 跨端 / 面试 / 架构)
💡 一起把技术学"明白",也用"到位"
持续写作,持续进阶。
愿我们都能在代码和生活里,走得更稳一点 🌱
文章目录
-
- [Debug 模式的"慢",不是一个原因](#Debug 模式的“慢”,不是一个原因)
- [Debug 会强行放大 build 成本](#Debug 会强行放大 build 成本)
- [Debug 会放大"无意义 rebuild"](#Debug 会放大“无意义 rebuild”)
- [Debug 模式下,滚动线程更"诚实"](#Debug 模式下,滚动线程更“诚实”)
-
- [Flutter 的滚动并不是"免费"的](#Flutter 的滚动并不是“免费”的)
- [Debug 卡 ≠ 可以忽略](#Debug 卡 ≠ 可以忽略)
-
- [可以忽略的 Debug 卡顿](#可以忽略的 Debug 卡顿)
- [不能忽略的 Debug 卡顿](#不能忽略的 Debug 卡顿)
- [一个典型的"Debug 放大问题"示例](#一个典型的“Debug 放大问题”示例)
- 正确拆分后的写法
-
- [Debug 下你会明显感觉到](#Debug 下你会明显感觉到)
- [为什么 Debug 模式更像"放大镜"](#为什么 Debug 模式更像“放大镜”)
- [Debug 模式下正确的性能判断方式](#Debug 模式下正确的性能判断方式)
- 总结
如果你写 Flutter 列表时没遇到过这种场景,基本可以确定你项目还不够复杂。
Debug 模式下滚动掉帧
Profile / Release 却突然顺了
很多人第一反应是:
"Debug 模式本来就慢,别管它。"
但问题是------
有些卡顿是 Debug 特有的,有些卡顿是在提前给你预警。
分不清这一点,后面一定会踩坑。
Debug 模式的"慢",不是一个原因
先说结论:
Debug 模式不是"整体慢",而是在几个关键路径上被故意放慢。
列表滚动正好踩中了所有雷区。
Debug 会强行放大 build 成本
Flutter 在 Debug 下,会刻意做更多事情。
包括但不限于:
- assert 校验
- Widget 树完整性检查
- layout / paint 的额外验证
- rebuild 边界检测
这些在 Release 模式里,基本都会被裁掉。
一个最直观的例子
dart
ListView.builder(
itemBuilder: (context, index) {
return Container(
padding: const EdgeInsets.all(16),
child: Text(items[index].title),
);
},
);
在 Debug 下:
- 每一次 scroll
- 每一次 item 进入可视区
- 每一次 build
都会触发更多的检查逻辑,所以你看到的是:
明明 item 很简单,但就是不顺
Debug 会放大"无意义 rebuild"
这是最容易被忽略,但最关键的一点。
看起来没问题的写法
dart
ListView.builder(
itemBuilder: (context, index) {
final theme = Theme.of(context);
final size = MediaQuery.of(context).size;
return Text(
items[index].title,
style: theme.textTheme.bodyLarge,
);
},
);
逻辑上没错,但在 Debug 下:
Theme.ofMediaQuery.of
都会建立依赖关系,结果就是:
任何上层变化,都会让整个列表重新 build
Debug 模式会把这件事表现得非常明显。
Debug 模式下,滚动线程更"诚实"
这是一个很多人不知道的事实。
Flutter 的滚动并不是"免费"的
在滚动过程中,Flutter需要:
- 计算哪些 item 进入/离开屏幕
- build 新 widget
- layout
- paint
在 Release 下:
- Skia 优化更激进
- JIT 变 AOT
- 编译器做了大量内联
而 Debug 下:
每一帧的压力都会原样暴露出来
所以你看到的"卡",很可能是:
- item build 太重
- widget 层逻辑过多
- rebuild 范围过大
而不是 Debug 的锅。
Debug 卡 ≠ 可以忽略
这里是真正的分界线。
可以忽略的 Debug 卡顿
- 纯文本列表
- 简单 item
- Release 下 FPS 明显稳定
这种情况,多半是 Debug 的额外开销。
不能忽略的 Debug 卡顿
- item 内部有状态
- 列表中有动画
- 滚动时频繁 rebuild 整个列表
- 滚动伴随 setState / notifyListeners
这些问题:
在 Release 里也会存在,只是被掩盖了。
一个典型的"Debug 放大问题"示例
问题写法
dart
class ListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final model = context.watch<ListModel>();
return ListView.builder(
itemCount: model.items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(model.items[index].title),
);
},
);
}
}
乍一看没问题。但这里有个致命点:
整个 ListView 订阅了 model
只要:
- 加一条数据
- 更新任意字段
整个列表都会 rebuild。Debug 下,掉帧极明显。
正确拆分后的写法
dart
class ListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: context.select<ListModel, int>(
(m) => m.items.length,
),
itemBuilder: (context, index) {
return ItemTile(index: index);
},
);
}
}
dart
class ItemTile extends StatelessWidget {
final int index;
const ItemTile({required this.index});
@override
Widget build(BuildContext context) {
final item = context.select<ListModel, Item>(
(m) => m.items[index],
);
return ListTile(
title: Text(item.title),
);
}
}
Debug 下你会明显感觉到
- 滚动更稳
- rebuild 范围被限制
- 掉帧次数减少
这不是 Debug 变快了,而是:
你终于写对了 Flutter 推荐的更新模型。
为什么 Debug 模式更像"放大镜"
可以用一句话总结:
Debug 模式不是用来跑性能的,是用来暴露结构问题的。
列表恰好是:
- rebuild 最频繁
- widget 最密集
- 状态最容易失控的地方
所以它最先"报警"。
Debug 模式下正确的性能判断方式
不要只凭"感觉"。
建议你一定要做三件事
-
打开 Performance Overlay
dartWidgetsApp.showPerformanceOverlay = true; -
用 Profile 模式看一眼
bashflutter run --profile -
对比 Debug / Profile 的行为差异
- 是否同样 rebuild
- 是否同样掉帧
总结
Flutter Debug 模式下列表"看起来很卡",并不是坏事。
真正危险的是:
Debug 很卡,但你选择无视它。
因为很多时候,Debug 不是在拖慢你,而是在提前救你。