一.关于State的个人介绍
每一个StatefulWidget
都会对应一个State
。state
主要用来存储StatefulWidget
的可变数据,当数据变化的时候触发UI更新。state
对象在StatefulWidget
重建时(例如父组件setState
触发重建),只要其中树的位置(key
相同)未变,关联的state
对象就不会被销毁,这就意味着State
的生命周期要比StatefulWidget
实例更长。
二.举个例子
下面是一段员工和老板关于薪资的代码,我们看一下经过一系列的操作,state
的周期函数都是怎么执行的,以及执行的顺序是什么样的。
/// 老板
class Boss extends StatelessWidget {
const Boss({super.key});
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
//导航到新路由
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return const Worker(salay: 7000,);
}),
);
},
child: const Scaffold(
body: Center(
child: Text("我是老板"),
),
),
);
}
}
点击老板可以进入员工薪资的页面
/// 打工的
class Worker extends StatefulWidget {
final int salay;
const Worker({super.key, required this.salay});
State<Worker> createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<Worker> {
/// 你得到的工资
int _salary = 0;
void initState() {
super.initState();
/// 初始化状态
_salary = widget.salay;
print('执行initState函数');
}
Widget build(BuildContext context) {
print('执行build函数');
return Scaffold(
body: Center(
child: TextButton(
child: Text('你得到的工资是:${_salary}'),
onPressed: () {
setState(() {
_salary = _salary + 2000;
});
},
),
),
);
}
void didUpdateWidget(Worker oldWidget) {
super.didUpdateWidget(oldWidget);
print('执行didUpdateWidget函数');
}
void deactivate() {
super.deactivate();
print('执行deactivate函数');
}
void dispose() {
super.dispose();
print('执行dispose函数');
}
void reassemble() {
super.reassemble();
print('执行reassemble函数');
}
void didChangeDependencies() {
super.didChangeDependencies();
print('执行didChangeDependencies函数');
}
}
在员工页面,每次点击一下你的工资,你的工资都会增加2000块。
1.运行应用,并打开“我是老板”的页面。此时点击“我是老板”,进入员工薪资页面,此时页面显示的是:你得到的工资是7000,可以看到工作台的输出结果是:
2.紧接着点击员工薪资页面上的“你得到的工资是7000”,页面上的内容变成了:你得到的工资是9000,控制台的输出结果是:
3.点击⚡️按钮热重载,控制台输出:
4.侧滑返回上一个页面,控制台输出:
三.关于State的各个生命周期函数
createState()
:当statefulWidget
被创建的时候,就会通过createState()
创建与之关联的state
对象,这个步骤是由框架自动调用的。
initState()
:在 State
对象被创建并且绑定到 Widget
上时调用,对于每一个State
对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。
didChangeDependencies()
:当State
对象的依赖发生变化时会被调用。典型的场景是当系统语言 Locale 或应用主题改变时,Flutter 框架会通知 widget 调用此回调。
需要注意,组件第一次被创建后挂载的时候(包括重创建)对应的
didChangeDependencies()
也会被调用。
build()
:主要用来构建widget子树,在如下常见被调用:
- 在调用
initState()
之后。 - 在调用
didUpdateWidget()
之后。 - 在调用
setState()
之后。 - 在调用
didChangeDependencies()
之后。 - 在State对象从树中一个位置移除后(会调用
deactivate()
)又重新插入到树的其他位置之后。
reassemble()
:仅存在于开发调试场景,在热重载(hot reload)时会被调用,Release模式下永远不会被调用。
didUpdateWidget ()
:在 widget 重新构建时,Flutter 框架会调widget.canUpdate
来检测 widget 树中同一位置的新旧节点,然后决定是否需要更新,如果widget.canUpdate
返回true则会调用此回调。
widget.canUpdate
会在新旧 widget 的key
和runtimeType
同时相等时会返回true,也就是说在在新旧 widget 的key
和runtimeType
同时相等时didUpdateWidget()
就会被调用。
deactivate()
:当 State
对象从树中被移除时,会调用此回调。
在一些场景下,Flutter 框架会将
State
对象重新插到树中,如包含此State
对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey
来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()
方法。
dispose()
:当 State
对象从树中被永久移除时调用;通常在此回调中释放资源。
四.你真的理解了吗?
尝试着思考回答一下这几个问题,看看你真的理解了吗?
- 当 StatefulWidget 第一次插入到 Widget 树时,哪些生命周期方法会被调用?顺序是什么?
首次插入时的正确顺序是:
initState → didChangeDependencies → build
- 当组件被永久移除时,哪些方法会被调用?它们的区别是什么?
当组件被永久移除时,deactivate,dispose分别会被调用,deactivate是在从组件树上移除的时候执行,dispose是在永久销毁的时候执行。
- 假设父组件更新了传递给子组件的 color 属性,此时子组件的 didUpdateWidget 和 didChangeDependencies 是否会触发?为什么?
假设父组件更新了传递给子组件的 color 属性,此时子组件的 didUpdateWidget会被触发,didUpdateWidget 触发是因为父组件传递的 Widget 配置(color)变化。didChangeDependencies 不触发,因为 color 是直接通过父组件传递的 Widget 属性,不是通过 InheritedWidget 依赖的全局状态。
- 如果子组件依赖了一个全局的 Theme(通过 Theme.of(context)),当主题颜色变化时,会触发哪个回调?
如果子组件依赖了一个全局的 Theme(通过 Theme.of(context)),当主题颜色变化时,会触发didChangeDependencies回调,因为 Theme.of(context) 依赖 InheritedWidget。
- 在 initState 中调用 setState 是否安全?为什么?
在 initState 中调用 setState 是安全的!Flutter 允许在 initState 中调用 setState,但需注意:
- 避免访问 BuildContext 依赖的 InheritedWidget(如 Theme.of(context)),因为此时组件尚未完全挂载。
- 典型场景:初始化数据后立即更新 UI。
- 如果父组件未发生任何变化,但子组件内部调用 setState,哪些生命周期方法会被触发?
如果父组件未发生任何变化,但子组件内部调用 setState,只会触发build 方法
- 在开发过程中执行热重载(Hot Reload)时,哪些生命周期方法会被调用?为什么这个回调在 Release 模式不存在?
在开发过程中执行热重载(Hot Reload)时,reassemble会被调用,因为Release模式下不调用
- 当使用 GlobalKey 将组件从树的一个位置移动到另一个位置时,deactivate 和 dispose 是否会触发?为什么?
当使用 GlobalKey 将组件从树的一个位置移动到另一个位置时,只会触发 deactivate,不会触发 dispose!
- 使用 GlobalKey 移动组件时,Flutter 会保留 State 对象,重新插入到新位置后不会调用 initState,而是直接复用之前的 State。
- 如果组件被移除后又重新插入到树中,它的 initState 会再次调用吗?为什么?
如果组件被移除后又重新插入到树中,它的 initState 不会再次调用,因为State 对象被复用时不会重新初始化。
- 以下代码有什么问题?如何修复?
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final StreamSubscription _subscription;
void initState() {
super.initState();
// 监听一个全局事件
_subscription = eventBus.on().listen((event) {});
}
Widget build(BuildContext context) {
return Text('Hello');
}
}
有问题,代码的问题在于未在 dispose 中取消订阅,导致内存泄漏!
void dispose() {
_subscription.cancel(); // 必须释放资源
super.dispose();
}
- 请描述以下操作的完整生命周期流程:
- 组件首次加载。
- 父组件更新属性。
- 依赖的 InheritedWidget 变化。
- 组件被移除并销毁。
首次加载:
- initState → didUpdateWidget → build
- 父组件更新属性:didChangeDependencies → build
- 依赖的 InheritedWidget 变化:didUpdateWidget → build
- 移除销毁:deactivate → dispose