Element
在“Widget简介”一节,我们介绍了Widget和Element的关系,我们知道最终的UI树其实是由一个个独立的Element节点构成。我们也说过组件最终的Layout、渲染都是通过 RenderObject 来完成的,从创建到渲染的大体流程是:
- 根据Widget生成Element
- 然后创建相应的 RenderObject 并关联到 Element.renderObject 属性上
- 最后再通过 RenderObject 来完成布局排列和绘制
Element就是Widget在UI树具体位置的一个实例化对象,大多数Element只有唯一的renderObject,但还有一些Element会有多个子节点,如继承自RenderObjectElement的一些类,比如 MultiChildRenderObjectElement。最终所有Element的RenderObject构成一棵树,我们称之为”Render Tree“即”渲染树“。总结一下,我们可以认为Flutter的UI系统包含三棵树:Widget树、Element树、渲染树。他们的依赖关系是: Element树根据Widget树生成,而渲染树又依赖于Element树,如图所示:

现在我们重点看一下Element,Element的生命周期如下
- Framework 调用 Widget.createElement 创建一个Element实例,记为 element
- Framework 调用 element.mount(parentElement,newSlot)
- mount方法中首先调用 element 所对应Widget的 createRenderObject 方法创建与 element 相关联的RenderObject对象,
- 然后调用 element.attachRenderObject 方法将element.renderObject添加到渲染树中插槽指定的位置(这一步不是必须的,一般发生在Element树结构发生变化时才需要重新attach)。插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。
- 当有父Widget的配置数据改变时,同时其 State.build 返回的Widget结构与之前不同,此时就需要重新构建对应的Element树
- 为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的 element,element 节点在更新前都会调用其对应Widget的canUpdate方法,如果返回true,则复用旧Element,旧的Element会使用新Widget配置数据更新,反之则会创建一个新的Element。Widget.canUpdate 主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回true,否则就会返回false。根据这个原理,当我们需要强制更新一个Widget时,可以通过指定不同的Key来避免复用。
- 当有祖先Element决定要移除element 时(如Widget树结构发生了变化,导致 element 对应的Widget被移除),这时该祖先Element就会调用 deactivateChild 方法来移除它,移除后element.renderObject也会被从渲染树中移除,然后Framework会调用 element.deactivate 方法,这时element状态变为“inactive”状态。
- “inactive”态的element将不会再显示到屏幕。为了避免在一次动画执行过程中反复创建、移除某个特定element,“inactive”态的 element 在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成“active”状态,Framework就会调用其 unmount 方法将其彻底移除,这时 element 的状态为defunct,它将永远不会再被插入到树中。
- 如果 element 要重新插入到Element树的其它位置,如element或element的祖先拥有一个 GlobalKey(用于全局复用元素),那么Framework会先将 element 从现有位置移除,然后再调用其activate方法,并将其renderObject重新attach到渲染树。
看完Element的生命周期,可能有些读者会有疑问,开发者会直接操作Element树吗? 其实对于开发者来说,大多数情况下只需要关注Widget树就行,Flutter框架已经将对Widget树的操作映射到了Element树上,这可以极大的降低复杂度,提高开发效率。 但是了解Element对理解整个Flutter UI框架是至关重要的,Flutter正是通过Element这个纽带将Widget和RenderObject关联起来,了解Element层不仅会帮助读者对Flutter UI框架有个清晰的认识,而且也会提高自己的抽象能力和设计能力。另外在有些时候,我们必须得直接使用Element对象来完成一些操作,比如获取主题Theme数据,具体细节将在下文介绍。
BuildContext
我们已经知道,StatelessWidget 和 StatefulWidget 的 build 方法都会传一个 BuildContext 对象:
Widget build(BuildContext context) {}
我们也知道,在很多时候我们都需要使用这个 context 做一些事,比如:
Navigator.push(context, route) //入栈新路由Theme.of(context) //获取主题Localizations.of(context, type) //获取Localcontext.size //获取上下文大小context.findRenderObject() //查找当前或最近的一个祖先RenderObject
那么 BuildContext 到底是什么呢,查看其定义,发现其是一个抽象接口类:
abstract class BuildContext {Widget get widget;BuildOwner get owner;RenderObject findRenderObject();Size get size;@Deprecated('Use dependOnInheritedElement instead. ''This feature was deprecated after v1.12.1.')InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect });InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });@Deprecated('Use dependOnInheritedWidgetOfExactType instead. ''This feature was deprecated after v1.12.1.')InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect });T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object aspect });@Deprecated('Use getElementForInheritedWidgetOfExactType instead. ''This feature was deprecated after v1.12.1.')InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType);InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();@Deprecated('Use findAncestorWidgetOfExactType instead. ''This feature was deprecated after v1.12.1.')Widget ancestorWidgetOfExactType(Type targetType);T findAncestorWidgetOfExactType<T extends Widget>();@Deprecated('Use findAncestorStateOfType instead. ''This feature was deprecated after v1.12.1.')State ancestorStateOfType(TypeMatcher matcher);T findAncestorStateOfType<T extends State>();@Deprecated('Use findRootAncestorStateOfType instead. ''This feature was deprecated after v1.12.1.')State rootAncestorStateOfType(TypeMatcher matcher);T findRootAncestorStateOfType<T extends State>();@Deprecated('Use findAncestorRenderObjectOfType instead. ''This feature was deprecated after v1.12.1.')RenderObject ancestorRenderObjectOfType(TypeMatcher matcher);T findAncestorRenderObjectOfType<T extends RenderObject>();void visitAncestorElements(bool visitor(Element element));void visitChildElements(ElementVisitor visitor);DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});List<DiagnosticsNode> describeMissingAncestor({ @required Type expectedAncestorType });DiagnosticsNode describeOwnershipChain(String name);}
那这个 context 对象对应的实现类到底是谁呢?我们顺藤摸瓜,发现 build 调用是发生在
- StatelessWidget 对应的 StatelessElement
- StatefulWidget 对应的 StatefulElement
StatelessElement 中
/// An [Element] that uses a [StatelessWidget] as its configuration.class StatelessElement extends ComponentElement {/// Creates an element that uses the given widget as its configuration.StatelessElement(StatelessWidget widget) : super(widget);@overrideStatelessWidget get widget => super.widget as StatelessWidget;@overrideWidget build() => widget.build(this);@overridevoid update(StatelessWidget newWidget) {super.update(newWidget);assert(widget == newWidget);_dirty = true;rebuild();}}
StatelessElement 中:
/// An [Element] that uses a [StatefulWidget] as its configuration.class StatefulElement extends ComponentElement {/// Creates an element that uses the given widget as its configuration.StatefulElement(StatefulWidget widget): _state = widget.createState(),super(widget) {assert(() {if (!state._debugTypesAreRight(widget)) {throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),ErrorDescription('The createState function for ${widget.runtimeType} returned a state ''of type ${state.runtimeType}, which is not a subtype of ''State<${widget.runtimeType}>, violating the contract for createState.',),]);}return true;}());assert(state._element == null);state._element = this;assert(state._widget == null,'The createState function for $widget returned an old or invalid state ''instance: ${state._widget}, which is not null, violating the contract ''for createState.',);state._widget = widget;assert(state._debugLifecycleState == _StateLifecycle.created);}@overrideWidget build() => state.build(this);/// The [State] instance associated with this location in the tree.////// There is a one-to-one relationship between [State] objects and the/// [StatefulElement] objects that hold them. The [State] objects are created/// by [StatefulElement] in [mount].State<StatefulWidget> get state => _state!;State<StatefulWidget>? _state;@overridevoid reassemble() {if (_debugShouldReassemble(_debugReassembleConfig, _widget)) {state.reassemble();}super.reassemble();}@overridevoid _firstBuild() {assert(state._debugLifecycleState == _StateLifecycle.created);try {_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);final Object? debugCheckForReturnedFuture = state.initState() as dynamic;assert(() {if (debugCheckForReturnedFuture is Future) {throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('${state.runtimeType}.initState() returned a Future.'),ErrorDescription('State.initState() must be a void method without an `async` keyword.'),ErrorHint('Rather than awaiting on asynchronous work directly inside of initState, ''call a separate method to do this work without awaiting it.',),]);}return true;}());} finally {_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);}assert(() {state._debugLifecycleState = _StateLifecycle.initialized;return true;}());state.didChangeDependencies();assert(() {state._debugLifecycleState = _StateLifecycle.ready;return true;}());super._firstBuild();}@overridevoid performRebuild() {if (_didChangeDependencies) {state.didChangeDependencies();_didChangeDependencies = false;}super.performRebuild();}@overridevoid update(StatefulWidget newWidget) {super.update(newWidget);assert(widget == newWidget);final StatefulWidget oldWidget = state._widget!;// We mark ourselves as dirty before calling didUpdateWidget to// let authors call setState from within didUpdateWidget without triggering// asserts._dirty = true;state._widget = widget as StatefulWidget;try {_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;assert(() {if (debugCheckForReturnedFuture is Future) {throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('${state.runtimeType}.didUpdateWidget() returned a Future.'),ErrorDescription( 'State.didUpdateWidget() must be a void method without an `async` keyword.'),ErrorHint('Rather than awaiting on asynchronous work directly inside of didUpdateWidget, ''call a separate method to do this work without awaiting it.',),]);}return true;}());} finally {_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);}rebuild();}@overridevoid activate() {super.activate();state.activate();// Since the State could have observed the deactivate() and thus disposed of// resources allocated in the build method, we have to rebuild the widget// so that its State can reallocate its resources.assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-opmarkNeedsBuild();}@overridevoid deactivate() {state.deactivate();super.deactivate();}@overridevoid unmount() {super.unmount();state.dispose();assert(() {if (state._debugLifecycleState == _StateLifecycle.defunct)return true;throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('${state.runtimeType}.dispose failed to call super.dispose.'),ErrorDescription('dispose() implementations must always call their superclass dispose() method, to ensure ''that all the resources used by the widget are fully released.',),]);}());state._element = null;// Release resources to reduce the severity of memory leaks caused by// defunct, but accidentally retained Elements._state = null;}@overrideInheritedWidget dependOnInheritedElement(Element ancestor, { Object? aspect }) {assert(ancestor != null);assert(() {final Type targetType = ancestor.widget.runtimeType;if (state._debugLifecycleState == _StateLifecycle.created) {throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('dependOnInheritedWidgetOfExactType<$targetType>() or dependOnInheritedElement() was called before ${state.runtimeType}.initState() completed.'),ErrorDescription('When an inherited widget changes, for example if the value of Theme.of() changes, '"its dependent widgets are rebuilt. If the dependent widget's reference to "'the inherited widget is in a constructor or an initState() method, ''then the rebuilt dependent widget will not reflect the changes in the ''inherited widget.',),ErrorHint('Typically references to inherited widgets should occur in widget build() methods. Alternatively, ''initialization based on inherited widgets can be placed in the didChangeDependencies method, which ''is called after initState and whenever the dependencies change thereafter.',),]);}if (state._debugLifecycleState == _StateLifecycle.defunct) {throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('dependOnInheritedWidgetOfExactType<$targetType>() or dependOnInheritedElement() was called after dispose(): $this'),ErrorDescription('This error happens if you call dependOnInheritedWidgetOfExactType() on the ''BuildContext for a widget that no longer appears in the widget tree ''(e.g., whose parent widget no longer includes the widget in its ''build). This error can occur when code calls ''dependOnInheritedWidgetOfExactType() from a timer or an animation callback.',),ErrorHint('The preferred solution is to cancel the timer or stop listening to the ''animation in the dispose() callback. Another solution is to check the ''"mounted" property of this object before calling ''dependOnInheritedWidgetOfExactType() to ensure the object is still in the ''tree.',),ErrorHint('This error might indicate a memory leak if ''dependOnInheritedWidgetOfExactType() is being called because another object ''is retaining a reference to this State object after it has been ''removed from the tree. To avoid memory leaks, consider breaking the ''reference to this object during dispose().',),]);}return true;}());return super.dependOnInheritedElement(ancestor as InheritedElement, aspect: aspect);}/// This controls whether we should call [State.didChangeDependencies] from/// the start of [build], to avoid calls when the [State] will not get built./// This can happen when the widget has dropped out of the tree, but depends/// on an [InheritedWidget] that is still in the tree.////// It is set initially to false, since [_firstBuild] makes the initial call/// on the [state]. When it is true, [build] will call/// `state.didChangeDependencies` and then sets it to false. Subsequent calls/// to [didChangeDependencies] set it to true.bool _didChangeDependencies = false;@overridevoid didChangeDependencies() {super.didChangeDependencies();_didChangeDependencies = true;}@overrideDiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {return _ElementDiagnosticableTreeNode(name: name,value: this,style: style,stateful: true,);}@overridevoid debugFillProperties(DiagnosticPropertiesBuilder properties) {super.debugFillProperties(properties);properties.add(DiagnosticsProperty<State<StatefulWidget>>('state', _state, defaultValue: null));}}
通过上面我们发现build传递的参数是 this,很明显!
- BuildContext 就是 StatelessElement。
- StatefulWidget 的 context 是 StatefulElement。
但 StatelessElement 和 StatefulElement 本身并没有实现 BuildContext 接口,继续跟踪代码,发现它们间接继承自Element 类,然后查看 Element 类定义,发现 Element 类果然实现了BuildContext接口:
class Element extends DiagnosticableTree implements BuildContext {...}
至此真相大白,BuildContext就是widget对应的Element
所以我们可以通过 context 在 StatelessWidget 和 StatefulWidget 的 build 方法中直接访问Element对象。我们获取主题数据的代码 Theme.of(context) 内部正是调用了Element的dependOnInheritedWidgetOfExactType()方法。
思考题:为什么build方法的参数不定义成Element对象,而要定义成BuildContext ?
进阶
我们可以看到 Element是Flutter UI框架内部连接widget和 RenderObject 的纽带
大多数时候开发者只需要关注widget层即可,但是widget层有时候并不能完全屏蔽 Element 细节,所以Framework在 StatelessWidget 和 StatefulWidget 中通过build方法参数又将 Element 对象也传递给了开发者,这样一来,开发者便可以在需要时直接操作 Element 对象。那么现在笔者提两个问题,请读者先自己思考一下:
- 如果没有widget层,单靠 Element 层是否可以搭建起一个可用的UI框架?如果可以应该是什么样子?
- Flutter UI框架能不做成响应式吗?
对于问题1,答案当然是肯定的,因为我们之前说过widget树只是 Element 树的映射,我们完全可以直接通过Element 来搭建一个UI框架。下面举一个例子:
我们通过纯粹的Element来模拟一个 StatefulWidget 的功能,假设有一个页面,该页面有一个按钮,按钮的文本是一个9位数,点击一次按钮,则对9个数随机排一次序,代码如下:
import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home:Scaffold(appBar: AppBar(title: Text('自定义UI框架')), body: CustomHome()),);}}class CustomHome extends Widget {// @override// Element createElement() {// return CustomElement(this);// }@overrideElement createElement() => CustomElement(this);}class CustomElement extends ComponentElement {CustomElement(Widget widget) : super(widget);String text = "123456789";@overrideWidget build() {Color primary = Theme.of(this).primaryColor; //1return GestureDetector(child: Center(child: FlatButton(child: Text(text,style: TextStyle(color: primary),),onPressed: () {var t = text.split("")..shuffle();text = t.join();markNeedsBuild(); //点击后将该Element标记为dirty,Element将会rebuild},),),);}}
- 上面build方法不接收参数,这一点和在StatelessWidget和StatefulWidget中build(BuildContext)方法不同。代码中需要用到BuildContext的地方直接用 this 代替即可,如代码注释1处Theme.of(this)参数直接传this即可,因为当前对象本身就是Element实例。
- 当text发生改变时,我们调用 markNeedsBuild() 方法将当前Element标记为dirty即可,标记为dirty的Element会在下一帧中重建。实际上,State.setState()在内部也是调用的markNeedsBuild()方法。
- 上面代码中build方法返回的仍然是一个widget,这是由于Flutter框架中已经有了widget这一层,并且组件库都已经是以widget的形式提供了,如果在Flutter框架中所有组件都像示例的HomeView一样以Element形式提供,那么就可以用纯Element来构建UI了HomeView的build方法返回值类型就可以是Element了。
- 如果我们需要将上面代码在现有Flutter框架中跑起来,那么还是得提供一个“适配器”widget将HomeView结合到现有框架中,下面CustomHome就相当于“适配器”:
class CustomHome extends Widget {@overrideElement createElement() => HomeView(this)}
现在就可以将 CustomHome 添加到widget树了,我们在一个新路由页创建它,最终效果如下如图所示:

对于问题2,答案当然也是肯定的,Flutter engine提供的dart API是原始且独立的,这个与操作系统提供的API类似,上层UI框架设计成什么样完全取决于设计者,完全可以将UI框架设计成Android风格或iOS风格,但这些事Google不会再去做,我们也没必要再去搞这一套,这是因为响应式的思想本身是很棒的,之所以提出这个问题,是因为笔者认为做与不做是一回事,但知道能不能做是另一回事,这能反映出我们对知识的理解程度。
总结
本节详细的介绍了 Element 的生命周期,以及它Widget、BuildContext的关系,也介绍了Element在Flutter UI系统中的角色和作用,我们将在下一节介绍Flutter UI系统中另一个重要的角色RenderObject。
