借助 const、key 和协调机制消除无用的重建,然后用 DevTools 进行测量。
在 Kodokon 中打开本课Flutter 操纵着三棵树:widget(不可变的、用完即弃的配置)、Element(承载状态的活实例)以及渲染对象(布局、绘制)。重建一个 widget 是廉价的;真正昂贵的是布局和层层传导的绘制。在一次重建过程中,Element.updateChild 会按顺序应用三条规则:如果新的 widget 与旧的完全相同(同一个实例),整棵子树就被短路;如果 Widget.canUpdate 为真(相同的 runtimeType、相同的 key),Element 就被保留下来,只是重新配置一下;否则,Element 会被销毁然后重新展开,连同状态一起。const 关键字利用的正是第一条规则:一个常量表达式会被编译器规范化,每次构建都返回同一个实例,identical 为真,于是整个分支被跳过。
import 'package:flutter/material.dart';
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() =>
_CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
const _Header(),
Text('Total: $_count'),
FilledButton(
onPressed: () => setState(() => _count++),
child: const Text('Increment'),
),
],
);
}
}
class _Header extends StatelessWidget {
const _Header();
@override
Widget build(BuildContext context) {
debugPrint('build _Header');
return const Text('Counter');
}
}对于子节点列表,协调机制首先按位置来配对。在没有 key 的情况下对有状态的条目重新排序,每个 Element 都会保留它这个位置上前一位占据者的状态:勾选框勾错了地方,文本框被张冠李戴。一个 Key 会改变配对规则:即便条目在列表中移动了,算法也能找到那个携带相同 key 的 Element。请在一个稳定的业务标识符上使用 ValueKey。GlobalKey 走得更远,它让你能够在不丢失状态的情况下把整棵子树移到新的父节点下,但它有实实在在的代价(全局注册表、贯穿整棵树的唯一性约束):把它留给像 Form 或移接父节点这样真正的需求。
import 'package:flutter/material.dart';
class TodoList extends StatelessWidget {
const TodoList({super.key, required this.items});
final List<String> items;
@override
Widget build(BuildContext context) {
return ListView(
children: [
for (final item in items)
Dismissible(
key: ValueKey(item),
onDismissed: (direction) {},
child: ListTile(title: Text(item)),
),
],
);
}
}先测量,再优化。在 DevTools 中,Performance 视图会追踪两个关键线程每一帧的耗时:UI(你的 Dart 代码:build、layout)和 raster(把图层栅格化)。在 60 Hz 下,预算大约是 16 毫秒。raster 一侧的卡顿靠优化你的 build 是修不好的:它来自绘制的开销(阴影、saveLayer、过大的图片)。启用 Track widget builds 来看清是谁在重建,用 debugRepaintRainbowEnabled 来可视化被重绘的区域,边框会在每次重绘时变色。RepaintBoundary 把一棵子树隔离到它自己的图层里:一个持续重绘的加载转圈就不会再弄脏它的邻居。
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugRepaintRainbowEnabled = true;
runApp(const DemoApp());
}
class DemoApp extends StatelessWidget {
const DemoApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
showPerformanceOverlay: true,
home: Scaffold(
body: Center(
child: RepaintBoundary(
child: CircularProgressIndicator(),
),
),
),
);
}
}