1. 引言
上一篇博客介绍了Widget,这篇文章主要介绍下StatelessWidget的相关知识点以及优化方案。
2. Widget 的两大分类
Flutter中的Widget 主要分为两大类:StatelessWidget(无状态组件) 和 StatefulWidget(有状态组件)。这是从组件是否持有可变状态的角度进行的划分。
这篇文章主要介绍下StatelessWidget的相关知识。
3.StatelessWidget
StatelessWidget是无状态组件。
1.定义
Dart
abstract class StatelessWidget extends Widget {
const StatelessWidget({super.key});
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
从定义中我们可以看到,StatelessWidget的方法很简单,重写了父类的key以及createElement和build方法。
StatelessWidget------纯函数式 UI:UI = f(配置 + BuildContext)。
2.工作原理
build() 通过组合一组更具体的 Widget 来描述 UI,递归展开,直到全部由 RenderObjectWidget 组成(它们直接创建 RenderObject,是递归终止点)。
StatelessWidget(抽象描述)
→ build() 返回更具体的 Widget
→ ...(递归)
→ RenderObjectWidget → createRenderObject() → RenderObject
3.使用场景
当UI只依赖自身配置 + BuildContext。
4.build的三种调用时机
1.首次插入树
以下面的代码为例:
Dart
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: FirstInsertDemo()));
class FirstInsertDemo extends StatefulWidget {
const FirstInsertDemo({super.key});
@override
State<FirstInsertDemo> createState() => _FirstInsertDemoState();
}
class _FirstInsertDemoState extends State<FirstInsertDemo> {
bool _show = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () => setState(() => _show = true),
child: const Text('插入 Widget'),
),
const SizedBox(height: 20),
if (_show) const MyCounter(label: '首次插入'),
],
),
),
);
}
}
class MyCounter extends StatelessWidget {
final String label;
const MyCounter({super.key, required this.label});
@override
Widget build(BuildContext context) {
print('[$label] build() 被调用'); // ← 观察控制台
return Text(label, style: const TextStyle(fontSize: 24));
}
}
操作 & 输出:
-
启动 App → 控制台没有
build()输出(MyCounter还没在树里) -
点按钮 → 控制台打印
[首次插入] build() 被调用 ← 这就是时机 ①
2.父类Widget配置变化
Dart
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: ParentChangeDemo()));
class ParentChangeDemo extends StatefulWidget {
const ParentChangeDemo({super.key});
@override
State<ParentChangeDemo> createState() => _ParentChangeDemoState();
}
class _ParentChangeDemoState extends State<ParentChangeDemo> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 父级每次 setState 都传一个新的 count 值
MyCounter(label: '计数: $_count'), // ← 配置变了
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('父级 setState'),
),
],
),
),
);
}
}
class MyCounter extends StatelessWidget {
final String label;
const MyCounter({super.key, required this.label});
@override
Widget build(BuildContext context) {
print('[MyCounter] build() 被调用, label="$label"'); // ← 观察控制台
return Text(label, style: const TextStyle(fontSize: 24));
}
}
操作 & 输出:
- 每次点按钮 →
_count++→ 父级setState→ 重建 - 控制台每次都打印:MyCounter build() 被调用, label="计数: N"
- 每次
label值不同 → 新 Widget 配置 →build()被调用 ← 这就是时机 ②
💡 如果改成
const MyCounter(label: '固定文字'),点按钮后build()不会再被调用 (因为const短路了 diff)。
3.依赖的InheritedWidget变化
Widget 通过
BuildContext读取了某个InheritedWidget(如Theme、MediaQuery),当那个 InheritedWidget 更新时 → 所有依赖它的 Widget 被标记为 dirty →build()被调用
Dart
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: InheritedChangeDemo()));
class InheritedChangeDemo extends StatefulWidget {
const InheritedChangeDemo({super.key});
@override
State<InheritedChangeDemo> createState() => _InheritedChangeDemoState();
}
class _InheritedChangeDemoState extends State<InheritedChangeDemo> {
Color _color = Colors.blue;
@override
Widget build(BuildContext context) {
// 用 Theme 作为 InheritedWidget 的载体
return Theme(
data: ThemeData(primaryColor: _color),
child: Scaffold(
body: const Center(
child: ColorReader(), // ← 这个 Widget 依赖 Theme
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_color = _color == Colors.blue ? Colors.red : Colors.blue;
});
},
child: const Icon(Icons.color_lens),
),
),
);
}
}
class ColorReader extends StatelessWidget {
const ColorReader({super.key});
@override
Widget build(BuildContext context) {
// 读取 InheritedWidget(Theme)
final color = Theme.of(context).primaryColor;
print('[ColorReader] build() 被调用, color=$color'); // ← 观察控制台
return Container(
width: 100,
height: 100,
color: color,
child: const Center(child: Text('看我变色', style: TextStyle(color: Colors.white))),
);
}
}
操作&输出:
- 启动 App → 控制台打印
[ColorReader] build() 被调用, color=MaterialColor(...) - 点 FAB 切换颜色 →
Theme数据变了 →ColorReader依赖了Theme.of(context) - 控制台再次打印:ColorReader build() 被调用, color=MaterialColor(...) ← 这就是时机 ③
- 每次切颜色都触发一次
5.优化技巧
1.减少build递归创建的节点数
下面有一个过渡嵌套的例子:
Dart
// ❌ 4 层嵌套,build 时创建 4 个 Element + RenderObject
Padding(
padding: EdgeInsets.all(8),
child: SizedBox(
width: 100,
child: Row(
children: [Expanded(child: Text('hello'))],
),
),
);
优化后:
Dart
// ✅ 1 个 Align 搞定定位,节点数从 4 降到 1
Align(
alignment: Alignment.center,
child: SizedBox(width: 100, child: Text('hello')),
);
同理:
-
多个
Container+Decoration叠层 → 换成 一个CustomPaint -
复杂的
Row/Column/Padding/SizedBox排列 → 换成CustomSingleChildLayout
为什么? 每个 Widget 都会变成 Element + 可能创建 RenderObject,嵌套越深,rebuild 时遍历的节点越多。
2.使用const构造函数
Dart
// ✅ 用户这样用你的 Widget:
const MyButton(); // Widget 层零分配,diff 时直接跳过
// 前提是你提供了 const 构造函数:
class MyButton extends StatelessWidget {
const MyButton({super.key}); // ← const 构造函数
@override
Widget build(BuildContext context) => Text('Click');
}
Widget 实例在编译期就确定了,整个 App 只有一个实例。
canUpdate 时:新旧Widget是同一个const对象 → identical()为true → 直接跳过子树 diff。
3.Stateless→Stateful 来缓存子树
Dart
// ❌ Stateless:每次父级重建,expensiveChild 都被重新 build
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
header,
expensiveChild, // ← 每次都重建
],
);
}
}
// ✅ Stateful:用 cached widget 避免重建
class _MyPageState extends State<MyPage> {
late final expensiveChild = ExpensiveWidget(); // 只创建一次
@override
Widget build(BuildContext context) {
return Column(
children: [header, expensiveChild], // expensiveChild 不变
);
}
}
4.把InheritedWidget依赖推到叶子节点
Dart
// ❌ Theme 变化 → 整个 MyWidget 重建
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // ← 依赖 Theme
return Container(
color: theme.primaryColor,
child: Text('Hello'), // ← Text 本身不依赖 Theme,但也被连带重建
);
}
}
// ✅ 把依赖 Theme 的部分抽成叶子 Widget
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: ColorfulText(), // ← 只有这个叶子依赖 Theme
);
}
}
class ColorfulText extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // ← Theme 变化只重建这个叶子
return Text('Hello', style: TextStyle(color: theme.primaryColor));
}
}
Theme变化时,只有ColorfulText 重建,Container 和上面的子树全部跳过。
5.Widget优于helper方法
Dart
// ❌ Helper 方法
Widget buildHeader() {
return Padding(padding: EdgeInsets.all(8), child: Text('Header'));
}
// 父 Widget 调 setState() → buildHeader() 返回的新 Widget 没有 key
// → Flutter 无法确认"这是同一个逻辑组件"
// → 整棵子树被销毁重建
Dart
// ✅ Widget 类
class Header extends StatelessWidget {
const Header({super.key});
@override
Widget build(BuildContext context) =>
Padding(padding: EdgeInsets.all(8), child: Text('Header'));
}
// 父 Widget 调 setState() → Header 是 const → canUpdate 直接跳过
// 即使不是 const,有 key 也能精确匹配,最小化重建
4.StatelessWidget知识点总结
StatelessWidget 是 Flutter 声明式 UI 的基本单元:它自己不画、不改、不存状态,只负责把"抽象描述"递归拆成"具体 RenderObject"------想让它快,就 const、少嵌套、Widget 类优于 helper 方法。