每日一题 Flutter#7,8 | 关于 State 两道简答题

第七题 第八题

最近在着手开发我的 《匠心星问》 ,它定位是一款 题库 应用,将集题目浏览、发布、解答、做题为一体。打算第一步先以 Flutter 为核心,准备题库资源。于是诞生《每日一题》 系列,准备精心设计一些 Flutter 的问题与解答,作为题库的养料。


第七题:

说说 State 状态类对象创建的时机

在 Flutter 框架中,statefulwidget 对界面交互起到至关重要的作用。而组件类本身只携带组件的配置数据。 状态数据的维护和界面的构建由对应的 State 类完成。理解State状态类,是认识Flutter的关键一。本题将探讨 State 状态类对象实例化的时机。

- -

1. 状态对象的诞生地 createState

从我们的老朋友 ChangeableCounter 组件来说,可以看出 StatefulWidget 的派生类,需要实现 createState 方法返回 State 对象。

dart 复制代码
class ChangeableCounter extends StatefulWidget {
  final Color color;

  const ChangeableCounter({super.key, required this.color});

  @override
  State createState() => _ChangeableCounterState(); //<--- 创建 State 对象
}

略...

所以这道题的答案很简单:

StatefulWidget 对应的 State 对象,在 createState 回调中被创建。


2. createState 回调是如何触发的

有求知欲的小伙伴可能会想知道:

StatefulWidget 派生类中的 createState 回调函数,是在什么时候被调用的呢?

开发者在代码中并没有调用过 createState 方法,所以这个方法是在 Flutter 框架层 被调用的。了解该函数触发的时机,对初步窥探 Flutter 框架的运行逻辑有很好的价值:

想要知道每个函数什么时候触发,其实非常简单。通过 断点调试 就可以很轻松的看到方法被调用时的堆栈信息。这也是分析源码的重要手段之一。


如下所示,为了看出 _ChangeableCounterState 对象创建时机,可以在改类中放一个构造函数,然后通过断点调试,查看构造函数的调用栈,图中可以看出 _ChangeableCounterState 对象的创建,确实是 ChangeableCounter#createState 方法触发的:


3. 元素与状态类

可以在下方的方法栈中,继续看 createState 的触发场合。如下所示:StatefulElememt 元素在实例化时,会触发 widget.createState 创建状态对象。该状态对象将作为 _state 成员被 StatefulElememt 对象持有:

感兴趣的朋友可以继续向下追踪,StatefulElememt 对象时合时被初始化的。这不是本题的焦点,后面有机会会单独出一题来介绍。


4. 总结

通过本题,从一个简单的 ChangeableCounter 示例出发,深入剖析了 Flutter 中 State 对象的创建过程。了解了以下几个关键点:

  • createState 方法的职责

每一个 StatefulWidget 都需要通过实现 createState 方法来生成其对应的 State 实例。这一步,是"状态对象"的诞生地。

  • createState 是何时被调用的?

尽管我们在业务代码中并未手动调用 createState,但在 Flutter 框架内部,StatefulElement 在构建时会主动触发该方法,并将创建的状态对象与自身绑定。

  • 调试是理解框架运行机制的利器

利用断点调试可以直观地观察方法的调用时机和调用栈,为深入理解 Flutter 的构建流程提供了极大的帮助。


通过本题,相信你对 StatefulWidgetState 又多了一点点认知。而这只是理解 Flutter 构建机制的一小步,后续我们还将继续探索 ElementWidgetState 之间的更多交互细节,敬请期待。


第八题

说说 State 抽象类持有的成员变量

在 Flutter 框架中,statefulwidget 对界面交互起到至关重要的作用。而组件类本身只携带组件的配置数据。 状态数据的维护和界面的构建由对应的 State 类完成。理解 State 状态类,是认识Flutter的关键一。本题将探讨 State 状态类中持有的成员变量。

- -

1. State 类中的三个成员

首先从日常的使用来看:

  • State 状态类可以访问 widget 来获取配置数据。
  • State 状态类可以访问 context 构建上下文获取信息。

所以这两个成员将会被 State 抽象类持有,通过源码可以更清楚的看出。State 中持有 T 类型的 _widget 成员,以及 StatefulElement? 类型的 _element 成员。我们可以发现在 State 中使用的 context,本质上就是 StatefulElement 对象,所以说 Element 其实一直伴随我们左右,并不是什么高不可攀的存在。

dart 复制代码
  T get widget => _widget!;
  T? _widget;
  
  BuildContext get context {
  return _element!;
 }
 
 StatefulElement? _element;

另外,还有一个 _StateLifecycle 类型的成员,用于记录 State 的生命周期,一般在调试环境生效,开发者可无法访问该字段。


2. 详细了解三个成员

_widget: 组件对象

与状态对象对应的 StatefulWidget 实例, 记当前的 UI 界面配置信息。

成员初始化 :该属性在调用 initState 之前由框架初始化。

State 对象在 StatefulElement 实例化时被创建。在构造方法中,还会为 state_widget 成员赋值。这里也就是状态类中 _widget 成员初始化 的场地:

成员的更新 : 调用 didUpdateWidget 方法前,该属性将被更新为新组件:

_widget 成员并不是一成不变的,当组件配置信息发生变化,上层节点更新时。下层的 StatefulWidget 对应的状态类并不会重新初始化。而是触发 didUpdateWidget 通知组件对象的变更,在此之前,会将 State._widget 成员更新为新组件:


_element: 元素对象

BuildContext 的运行时对象,通过构建上下文,可以访问对应组件在树中的信息,得到上层节点存储的共享信息,比如主题、导航、媒体查询等

成员初始化 :该属性在元素实例化后,与状态类绑定

StatefulElement 实例化之后,状态类对象会将 _element 赋值为当前的元素对象,从而实现绑定:

成员的更新 : 调用 StatefulElement#unmount 取消挂载时,状态类的 _element 成员被置空。

总的来看,在 State 状态对象的生命周期内,对应的 BuildContext 是不会发生变化的。


_debugLifecycleState: 生命周期

_StateLifecycle 枚举有四个成员,默认是 created:

dart 复制代码
/// 在启用断言(asserts)时,用于追踪 [State] 对象的生命周期。
enum _StateLifecycle {
  /// [State] 对象已被创建,此时会调用 [State.initState] 方法。
  created,

  /// [State.initState] 方法已被调用,但 [State] 对象尚未准备好进行构建。
  /// 此时会调用 [State.didChangeDependencies] 方法。
  initialized,

  /// [State] 对象已准备好进行构建,且尚未调用 [State.dispose] 方法。
  ready,

  /// [State.dispose] 方法已被调用,[State] 对象已无法再构建。
  defunct,
}

在状态对象生命周期回调的相关时机,会更新到对应的枚举值。比如 initState 之后会更新为 initialized 状态, 在 didChangeDependencies 之后,会更新为 ready 状态:

它们都在 assert 断言中被赋值,所以只在 debug 模式下生效。


3. 总结

从源码层面看,State 类本身其实非常简洁,仅持有必要的对象引用:

  • _widget(当前绑定的 StatefulWidget 实例)、
  • _element(对应的 StatefulElement,提供构建上下文)、
  • 以及 _debugLifecycleState(仅在调试模式下用于追踪生命周期状态)

State 并不直接持有子组件或 UI 的树形结构,它只是负责维护自身的状态数据,并通过 build() 方法生成组件树来描述 UI 界面。框架会根据是否重建来更新 _widget 并触发 didUpdateWidget


如果你有其他的看法,或者有什么想要的题目、或者想提供题目和答案,都欢迎在评论区留言。更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。

相关推荐
MrSkye22 分钟前
React入门:组件化思想?数据驱动?
前端·react.js·面试
前端农民晨曦36 分钟前
深入浏览器事件循环与任务队列架构
前端·javascript·面试
穗余1 小时前
WEB3全栈开发——面试专业技能点P6后端框架 / 微服务设计
面试·职场和发展
用户2018792831671 小时前
如何利用AI工具快速学习Android源码
android
Z_haha2 小时前
js模块化之commonjs与es6模块化
前端·javascript·面试
icc_tips2 小时前
Flutter Async 与 Async*
flutter
穗余2 小时前
WEB3全栈开发——面试专业技能点P5中间件
中间件·面试·职场和发展
音视频牛哥2 小时前
Android 平台RTSP/RTMP播放器SDK接入说明
android·音视频·大牛直播sdk·rtsp播放器·rtmp播放器·rtmp低延迟播放·rtmpplayer
江城开朗的豌豆3 小时前
JavaScript篇:偷懒也有理!事件代理让我少写一半代码
前端·javascript·面试
江城开朗的豌豆3 小时前
Proxy:JavaScript中的'变形金刚',让你的对象为所欲为!
前端·javascript·面试