💡 示例代码:
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String msg = "Hello";
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(msg), // <=== 关注点
ElevatedButton(
onPressed: () {
setState(() {
msg = "World";
});
},
child: Text("Change"),
)
],
);
}
}
🔄 当点击按钮 setState()
时,发生了什么?
我们逐步分析:
✅ 第 1 步:调用 setState()
- 通知框架:“State 改变了!”
- 框架会把当前 Widget 标记为 dirty,等待下一帧重建。
✅ 第 2 步:执行 build()
- Flutter 会重新调用
build()
方法,返回新的 Widget 树。 Text(msg)
→ 变成了Text("World")
,但注意:它依然不是const
,所以是一个新的 Widget 实例。
✅ 第 3 步:Flutter diff 新旧 Widget 树
- 找到旧的
Text("Hello")
Widget → 现在变成新的Text("World")
- 它们类型相同(Text),且 key 也都为 null,满足:
Widget.canUpdate(old, new) == true
✅ 所以: Flutter 会复用原来的 Element 和 RenderObject,仅仅调用 Text
的 update()
。
✅ 第 4 步:Text 的 updateRenderObject()
被调用
Text
Widget 是LeafRenderObjectWidget
,它内部绑定的是一个RenderParagraph
。- 当 Widget 内容变了(
data != oldWidget.data
),会执行:
@override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
renderObject.text = buildTextSpan(); // 更新 TextSpan
}
✅ 第 5 步:RenderObject 被标记 dirty → 触发重绘
- 由于
text
内容变化,RenderParagraph
会重新 layout 和 paint; - 最终新的文字
"World"
被渲染到屏幕上。
🎯 总结整个流程:
阶段 | 是否变化 | 是否复用 |
---|---|---|
Widget 实例 | ✅ 新建 | ❌ 不复用 |
Element 实例 | ❌ 不变 | ✅ 复用 |
State(如果有) | ❌ 不变 | ✅ 复用 |
RenderObject | ❌ 不变 | ✅ 复用(但会更新内容) |
✅ 所以你看到的效果是:
-
文字从
"Hello"
→"World"
; -
Flutter 并没有销毁和重新创建整个
Text
Widget 的 Element 或 RenderObject; -
它只是:
- diff 出
Text
的内容变化, - 调用
updateRenderObject
更新文字内容, - 触发部分重绘(layout 和 paint)。
- diff 出
🧠 再延伸一句话:
Text(msg)
只要不是const
,每次 build 都是新的 Widget 实例;但只要类型和 key 没变,Element 和 RenderObject 都可以复用,实现高效更新。