1. StatelessWidget 与 StatefulWidget 本质区别是什么?
-
StatelessWidget:不可变,
build方法只依赖构造时传入的参数和外部环境,不会随时间变化。 -
StatefulWidget:拥有一个可变的
State对象,当状态变化时调用setState触发build重建 UI。
⚠️ 注意事项:
StatefulWidget的 Widget 本身仍是不可变的(其配置不变),可变部分存放在独立的State对象中。- 不要试图在
StatefulWidget里直接保存可变字段。
2. 什么是 Element?它和 Widget、RenderObject 的关系是怎样的?
- Widget:UI 的配置描述(轻量、不可变),用于创建 Element。
- Element:Widget 在树中的实例化节点,负责管理生命周期、对比前后 Widget 并更新 RenderObject。
- RenderObject:负责实际的布局、绘制和命中测试,是渲染树的节点。
- 关系:Widget → 创建 → Element → 持有 → RenderObject。三者协同完成“配置 → 管理 → 渲染”流程。
⚠️ 注意事项:
- 大部分开发者只操作 Widget,Element 由框架维护。
- 性能优化时可以通过 GlobalKey 访问 Element。
3. Flutter的三棵树(Widget、Element、RenderObject)是如何协同工作的?
-
Widget 树:开发者声明的 UI 结构(每个
build返回的新 Widget)。 -
Element 树:框架根据 Widget 树创建并维护的节点,通过
mount、update管理差异。 -
RenderObject 树:Element 中的
renderObject属性构成,执行layout、paint。
流程:帧开始时,框架调用 build 生成新 Widget 树 → 与现有 Element 树进行 canUpdate 比较 → 决定复用、更新或重建 Element → 更新对应的 RenderObject → 执行布局绘制。
⚠️ 注意事项:
canUpdate的判断依据是runtimeType和key,因此合理使用Key能提高复用效率。
4. BuildContext 是什么?它有什么用?
BuildContext 是 Element 的代理接口,代表当前 Widget 在树中的位置。
常用能力:
- 查找祖先 Widget(
dependOnInheritedWidgetOfExactType、findAncestorStateOfType) - 获取主题、尺寸、媒体信息(
MediaQuery.of(context)) - 注册依赖,当上层
InheritedWidget变化时重建
⚠️ 注意事项:
BuildContext不应在异步回调中长期保存,因为 Widget 可能被重建导致 context 失效。优先使用 context 的瞬时方法。
5. StatefulWidget 的 State 保存在哪里?为什么 Widget 可以重建而 State 不会丢失?
State 对象由 Element 持有,当 Widget 重建时,框架会复用同一类型的 Element 及其关联的 State(只要 canUpdate 返回 true)。因此 State 的生命期长于 Widget 实例。
⚠️ 注意事项:
- 切勿通过 Widget 引用
State,应使用GlobalKey获取。
6. 什么是 GlobalKey?它的作用及潜在问题?
GlobalKey 在整个应用中唯一标识一个 Widget 或 Element,可以访问其 State、尺寸、位置等。常用于跨 Widget 通信、表单验证、动画等。
⚠️ 注意事项:
GlobalKey是开销较大的对象,应避免创建过多。- 移动 Widget 时需要保留原
GlobalKey来维持状态。
7. Key 有哪些子类?何时需要使用?
Key 分为 LocalKey(ValueKey、ObjectKey、UniqueKey)和 GlobalKey。ValueKey 根据某个值识别,UniqueKey 每次创建都不同。当需要保持 Widget 状态识别(如列表项移动、有状态子 Widget 重排序)时必须使用 Key。
⚠️ 注意事项:
- 只在必要时使用,例如
AnimatedList、ReorderableListView。
8. 什么是 Element 的 mount 和 unmount 过程?
-
mount是 Element 插入到树时调用,它创建 RenderObject 并附加。 -
unmount是从树中移除时调用,清理资源。 - 对于 StatefulElement,
mount会调用initState。
⚠️ 注意事项:
- 开发者通常不直接操作这些生命周期,但理解有助于调试性能问题。
9. BuildContext 为何能从 State 中获取?它们的关系是?
State 对象创建时会持有一个 Element 的引用,而 BuildContext 正是该 Element 的抽象接口。因此 State.context 就是该 Element。
⚠️ 注意事项:
- 在
initState中访问context是安全的,但不能在该方法中调用依赖InheritedWidget的方法。
10. 当父 Widget 重建时,子 Widget 一定会重建吗?
不一定。如果子 Widget 的 runtimeType 和 key 与之前相同,框架会复用 Element 且仅当子 Widget 的 canUpdate 为 true 时更新其配置,但子 Widget 的 build 方法是否调用取决于子 Widget 本身是否发生变化。
⚠️ 注意事项:
- 使用
const构造函数可以让子 Widget 在参数不变时不重建。
11.什么是 RenderObject 的 layout 和 paint 流程?
-
layout过程接收父级传来的约束,计算自身大小并设置子节点位置。 -
paint过程在已确定的大小和位置上进行绘制,通过Canvas渲染。
⚠️ 注意事项:
- 避免在
paint中做复杂的计算或分配临时对象,否则影响性能。
12. RepaintBoundary 如何提升性能?原理是什么?
答案: 创建一个新的绘制层(Layer),使子树的重绘被限制在该层内,不会触发父级重绘。常用于动画或高频更新的区域。
⚠️ 注意事项: 过度使用会合成更多层,增加 GPU 内存和合成开销。仅在检测到重绘范围过大时使用。
13 InheritedWidget 更新时,如何避免所有依赖重建?
答案: updateShouldNotify 返回 false 可以阻止通知。但更细粒度的做法是在子组件中使用 context.select(Provider 的 Selector)或手动只依赖局部数据。
⚠️ 注意事项: InheritedWidget 的依赖机制会使得任何依赖它的 build 方法在变化后重新执行。应拆分数据范围。
14. 什么是 Element 的 inflateWidget 方法?
答案: inflateWidget 负责根据 Widget 创建或复用 Element,并递归挂载到树中。是框架构建过程的核心方法。
⚠️ 注意事项: 普通开发者不需要重写,但在编写自定义 RenderObjectWidget 时可能涉及。