
网罗开发 (小红书、快手、视频号同名)
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验 。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索"展菲",即可纵览我在各大平台的知识足迹。
📣 公众号"Swift社区",每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友"fzhanfei",与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
-
- 前言
- [rebuild 本身不是问题,失控才是](#rebuild 本身不是问题,失控才是)
- [Flutter 的 build 到底是怎么被触发的?](#Flutter 的 build 到底是怎么被触发的?)
- [最常见的问题:setState 用得太"随手"](#最常见的问题:setState 用得太“随手”)
- [拆 Widget,是最立竿见影的优化手段](#拆 Widget,是最立竿见影的优化手段)
- [const Widget 不是装饰,是性能保障](#const Widget 不是装饰,是性能保障)
- [InheritedWidget 和 Provider 为什么容易"连坐"?](#InheritedWidget 和 Provider 为什么容易“连坐”?)
- [用 Selector 精准控制 rebuild](#用 Selector 精准控制 rebuild)
- [Riverpod 中的 rebuild 控制思路](#Riverpod 中的 rebuild 控制思路)
- [用 DevTools 找 rebuild 热点,而不是靠感觉](#用 DevTools 找 rebuild 热点,而不是靠感觉)
- 总结
前言
如果你做 Flutter 开发一段时间,大概率遇到过这种情况:
只是点了一下按钮,
页面却像"抖"了一下,
列表滚动开始掉帧,
动画也没以前顺了。
更让人困惑的是------
你明明没改 UI 结构,只是改了一个状态。
这篇文章就专门聊一个在 Flutter 项目里几乎绕不开的问题 :
页面为什么会频繁 rebuild,以及我们该如何一步步把它控制住。
不会只停留在概念解释,我会结合真实开发场景,把 build 的触发链路拆清楚,并配上可以直接跑的 Demo。
rebuild 本身不是问题,失控才是
很多人一听到 rebuild 就条件反射式紧张,其实这是个常见误解。
在 Flutter 里:
- build 并不等于重绘
- build 也不等于重新创建 RenderObject
- build 更像是"重新生成一份 UI 描述"
Flutter 的设计前提就是:
build 要轻量、可频繁调用。
真正影响性能的,从来不是"有没有 rebuild",而是:
- rebuild 的范围是不是太大
- 不该 rebuild 的地方有没有被牵连
- build 里有没有做了重活
后面的所有优化,其实都围绕一个核心目标:
把 rebuild 限制在最小、最合理的范围内。
Flutter 的 build 到底是怎么被触发的?
很多教程都会简单地说一句:
setState 会触发 build
这句话没错,但太笼统了。
从底层角度看,真实流程更像这样:
状态发生变化
↓
Element 被标记为 dirty
↓
Flutter 在下一帧统一调度 build
↓
重新生成 Widget 子树
有两个关键点经常被忽略:
- setState 并不会立刻重建 UI
- Flutter 会在一帧内合并多次状态变更
这也是 Flutter 在高频交互下依然能保持流畅的原因之一。
最常见的问题:setState 用得太"随手"
下面这个例子,基本是很多人项目里的真实写照。
dart
class CounterPage extends StatefulWidget {
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int count = 0;
@override
Widget build(BuildContext context) {
print('CounterPage build');
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () {
setState(() {
count++;
});
},
child: Text('Add'),
),
HeavyWidget(),
],
),
);
}
}
class HeavyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('HeavyWidget build');
return Container(
height: 200,
color: Colors.blue,
);
}
}
你点一次按钮,控制台会输出:
CounterPage build
HeavyWidget build
问题就来了:
HeavyWidget 和 count 有任何关系吗?
没有,但它还是被 rebuild 了。
原因也很简单:
setState 作用在父节点,整个子树都会被标记为需要重建。
拆 Widget,是最立竿见影的优化手段
很多性能问题,并不是 Flutter 本身的问题,而是页面结构的问题。
最常见的模式是:
- 整个页面是一个 StatefulWidget
- 所有状态都集中在最外层
- 任意一个字段变化,整个页面重建
其实只要稍微调整结构,就能解决一大半问题。
dart
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Column(
children: [
CounterView(),
HeavyWidget(),
],
),
);
}
}
class CounterView extends StatefulWidget {
@override
State<CounterView> createState() => _CounterViewState();
}
class _CounterViewState extends State<CounterView> {
int count = 0;
@override
Widget build(BuildContext context) {
print('CounterView build');
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () {
setState(() {
count++;
});
},
child: Text('Add'),
),
],
);
}
}
现在再点按钮,你会发现:
- 只有 CounterView 在 rebuild
- HeavyWidget 完全不会受影响
在真实业务里,这种拆分非常常见,比如:
- 筛选条件和列表分离
- 表单项和提交按钮分离
- 动画区域和业务状态分离
const Widget 不是装饰,是性能保障
很多人知道要写 const,但并不知道它的真正意义。
dart
Text('Hello');
和
dart
const Text('Hello');
在频繁 rebuild 的场景下,差别非常明显。
const Widget 的核心价值在于:
- Flutter 可以直接复用已有实例
- 避免不必要的对象创建和比较
在拆好 Widget 之后,再配合 const,效果会非常明显。
dart
class StaticHeader extends StatelessWidget {
const StaticHeader({super.key});
@override
Widget build(BuildContext context) {
return Text(
'Static Header',
style: TextStyle(fontSize: 24),
);
}
}
哪怕父节点 rebuild,这类 Widget 的成本几乎可以忽略。
InheritedWidget 和 Provider 为什么容易"连坐"?
当你在 build 里写下:
dart
final user = context.watch<UserModel>();
实际上发生的是:
- 当前 Widget 订阅了 UserModel
- UserModel 任何字段变化
- 所有依赖它的 Widget 都会 rebuild
如果 UserModel 很"胖",问题就会被无限放大。
用 Selector 精准控制 rebuild
假设有这样一个模型:
dart
class UserModel extends ChangeNotifier {
String name;
int age;
UserModel(this.name, this.age);
void updateAge(int newAge) {
age = newAge;
notifyListeners();
}
}
不推荐的写法是:
dart
Text(context.watch<UserModel>().name);
更合理的方式是:
dart
Selector<UserModel, String>(
selector: (_, model) => model.name,
builder: (_, name, __) {
return Text(name);
},
);
这样一来:
- age 变化,不会 rebuild
- name 变化,才会触发更新
这在列表页、用户信息页里非常重要。
Riverpod 中的 rebuild 控制思路
如果你使用 Riverpod,本质思路是一样的。
dart
final countProvider = StateProvider<int>((ref) => 0);
尽量避免在大 Widget 中直接 watch:
dart
final count = ref.watch(countProvider);
可以通过拆 Widget,或者使用 select,让 rebuild 更精确。
用 DevTools 找 rebuild 热点,而不是靠感觉
Flutter DevTools 提供了非常实用的工具:
- 打开 Performance
- 勾选 rebuild / repaint 高亮
页面中频繁闪烁的区域,通常就是优化突破口。
同时,一个非常原始但有效的方法是:
dart
@override
Widget build(BuildContext context) {
debugPrint('build: ${runtimeType}');
return ...
}
在复杂页面中,这个手段非常好用。
总结
如果你不想每次都重新分析,这里有一套通用原则:
- rebuild 本身不是问题,范围失控才是
- StatefulWidget 尽量下沉
- 能 const 的 Widget 一定要 const
- 拆 Widget 是最优先的优化方式
- 状态监听一定要精确
- 用工具定位,而不是凭感觉猜
这些经验,基本来自所有中大型 Flutter 项目的踩坑总结。