likes
comments
collection
share

Flutter 必知必会系列——三颗树到底是什么

作者站长头像
站长
· 阅读数 12

Flutter 文档中有一句话 An obvious simplification would be to combine these trees into one tree,可以将树简化,但是综合了:PerformanceClarityType safety 没有这么做,下面我们就看看这三棵树是什么以及三棵树是怎么关联起来的。

Widget到底是什么

Widget 简介

Widgets are the building blocks of a Flutter app’s user interface, and each widget is an immutable declaration of part of the user interface.

Widget 就是我们开发者构建的 UI 代码块,写间距用 Padding、写流式布局用 Wrap。那么 Widget 到底是什么呢? 我们来看 Widget 的官方描述(2.2 SDK):


Describes the configuration for an [Element].

Widgets are the central class hierarchy in the Flutter framework. A widget
is an immutable description of part of a user interface. Widgets can be
inflated into elements, which manage the underlying render tree.

Widgets themselves have no mutable state (all their fields must be final).
If you wish to associate mutable state with a widget, consider using a
[StatefulWidget], which creates a [State] object (via
[StatefulWidget.createState]) whenever it is inflated into an element and
incorporated into the tree.

 A given widget can be included in the tree zero or more times. In particular
 a given widget can be placed in the tree multiple times. Each time a widget
 is placed in the tree, it is inflated into an [Element], which means a
 widget that is incorporated into the tree multiple times will be inflated
 multiple times.
 

  • Widget 仅仅是 Element 的描述性信息,是一个配置文件。需要将可以将 Widget 的信息 inflated 到 Element中。这里提到了第二个类 —— Element,它管理渲染。

  • Widget 是整个 Flutter 类体系的核心,负责和用户的交互。

  • Widget 本身是不变的。因此它的属性都是 final 的。至于 StatefulWidget 它的状态维护在第三个对象 State 中,并不是 Widget 中,因此 Widget 本身还是不变的。

  • Widget 可以在 Widget 树上的被放置多次。比如把 Text 组件写成成员变量,挂到多个节点上。

总结一下:简单来说 Widget 就是代表了我们的 UI,和 Element 的关系就像是我们 Android 中 xml 文件和 View 的关系,仅仅只是个配置。

举个例子

我们开发的:Text("sss"), 相当于告诉 Element :请帮我显示一个文本。

有哪些 Widget

Flutter 的设计原则是概念尽可能少,这个概念对应到代码就是 Flutter 的顶层类或者叫做基类, Widget 类就是一个概念的体现,它的直接子类就是开发者可以使用的 UI 种类。如下图:

Flutter 必知必会系列——三颗树到底是什么

StatelessWidget

无状态就是没有 State 对象的 Widget,只需重写 build 方法就可以显示 UI。我们用它来显示没有业务逻辑的区域,我们经常用的 ContainerTextBuilder 等就是 StatelessWidget。

Flutter 必知必会系列——三颗树到底是什么

我们知道 Widget 会被 inflated 成 Element,StatelessWidget 对应的 Element 是 StatelessElement

StatefulWidget

有状态 就是会创建 State 对象的 Widget ,在 State 中可以根据运行时的不同数据,显示不同 UI 。官方的状态管理文章说,单一状态的 UI 可以使用 StatefulWidget 就可以了。

我们常用的 TabBarAppBar 等就是 StatefulWidget。

可能有同学说了,不是说 Widget 是不可变的吗?

是这样的 Widget,确实是不可变的,它的所有属性都是 final 的,这种类型的 Widget 将显示的逻辑放到了 State 中。

Flutter 必知必会系列——三颗树到底是什么

State 中的 build 方法就是我们显示 UI 的地方。

StatefulWidget 对应的 Element 是 StatefulElement。

⚠️注意: StatefulElement 和 StatelessElement 都是组合型的 Widget,并直接不参与绘制等过程,都是 ComponentElement 的子类。所以在它们以及他们对应的 Widget 的代码中,没有任何的绘制相关的信息。

ProxyWidget

代理类型 Widget,并不用于直接显示 UI,比如常见的 InheritedWidget、hook 父节点数据的ParentDataWidget 等等。

这个类型的 Widget 数量是最少的,一般是作为功能实现的支持,比如我们熟知的 MediaQuery 节点。


abstract class ProxyWidget extends Widget {

  const ProxyWidget({ Key? key, required this.child }) : super(key: key);

  final Widget child;
}

它显示的内容就是 child 字段。

RenderObjectWidget

渲染类型 Widget,负责为 Element 创建用于渲染的 RenderObject,创建的 RenderObject 中就包含了布局、绘制等流程。系统为我们提供的组件大多数都是渲染类型的。比如:Padding、Opacity、Row 等等。

这种类型的 Widget 重写比较复杂,需要指定渲染过程。我们以常用 Offstage 组件来看大概的流程。

class Offstage extends SingleChildRenderObjectWidget {
 
  const Offstage({ Key? key, this.offstage = true, Widget? child })
    : assert(offstage != null),
      super(key: key, child: child);
      
  //... 省略代码
  
  //创建了 Render
  @override
  RenderOffstage createRenderObject(BuildContext context) => RenderOffstage(offstage: offstage);

  //创建了 Element
  @override
  _OffstageElement createElement() => _OffstageElement(this);
}

对于渲染类型的 Widget ,既要指定 Element(_OffstageElement),也要指定 RenderObject (RenderOffstage)。看到没有,Widget 不保留任何状态,它的方法都是供别人调用的。


class RenderOffstage extends RenderProxyBox {
 
  RenderOffstage({
    bool offstage = true,
    RenderBox? child,
  }) : assert(offstage != null),
       _offstage = offstage,
       super(child);

   //... 代码简化
  
  @override
  void performLayout() {
    if (offstage) {
      child?.layout(constraints); 
    } else {
      super.performLayout();
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (offstage)
      return;
    super.paint(context, offset);
  }

}

我们看到它的内部的布局和绘制,都是 offstage 来决定的,只要 offstage 不是 true,都不进行子节点的绘制和布局。所以,起到了不显示的效果。

通过这几类 Widget,就可以显示任何 UI,实现任何功能了。比如开发者想显示 UI ,那么可以先从系统提供的各类 RenderObjectWidget 找一下。如果没有,可以尝试使用 StatelessWidget、StatefulWidget 组合各类 Widget。 

如果不想显示UI,想提供某种功能,可以尝试重写 ProxyWidget,我们常用的 Provider 就是 InheritWidget。流式布局的流式就是 ParentDataWidget。

从代码的角度怎么表达这种关系呢?

Flutter 必知必会系列——三颗树到底是什么

每一个 Widget 都有其 Element 相对应。

所以 Flutter 中,大家面对的都是一个个的 Widget。EveryThing is Widget

上面提到了 Element ,那么 Element 是什么呢?Element 和 Widget 关系是怎么绑定的呢?

Element 是什么

Element 简介

我们在开发的过程中,很少会直接涉及到 Element 的开发,但是当我们排查问题的时候,往往会断点到Element 中,尤其是排查数据和UI不同步的问题的时候。并且

我们先看一下Element的官方描述

An instantiation of a [Widget] at a particular location in the tree.
Widgets describe how to configure a subtree but the same widget can be used
to configure multiple subtrees simultaneously because widgets are immutable.

An [Element] represents the use of a widget to configure a specific location
in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget
for this location.

Elements form a tree. Most elements have a unique child, but some widgets
(e.g., subclasses of [RenderObjectElement]) can have multiple children.

从上面我们可以看到:

  • Element 是 Widget 的实际内容,并且会根据 Widget 的位置,配置自己在书中的位置

  • 在程序运行过程中,与 Element 相对应的 Widget 会改变,比如父 Widget 重新构建了,这样其子树也会改变。

  • Element 也会形成一棵树,有的 Element 有一个子节点,有的有多个子节点

这就是为什么会有下面的图:

Flutter 必知必会系列——三颗树到底是什么

Element 树形成

那么是什么时候形成 Element 树的呢?下面我们先通过生命周期来看。

Flutter 必知必会系列——三颗树到底是什么

关键的方法就这么几个,因为本次主要是介绍,三棵树的关系,因此并不详解更新的过程。

我们从父节点的视角开始看,父节点想要显示子节点的时候,子节点的 Element 的 mount 方法是生命周期的第一个方法。父节点移出子节点的时候就会调用 Element 的 unmount 方法。

mount 方法

我们先看 mount 方法的描述。

/// Add this element to the tree in the given slot of the given parent.

/// The framework calls this function when a newly created element is added to
/// the tree for the first time. Use this method to initialize state that
/// depends on having a parent. State that is independent of the parent can
/// more easily be initialized in the constructor.
///
/// This method transitions the element from the "initial" lifecycle state to
/// the "active" lifecycle state.
/// ...

mount 就完成了一件事

  • 将本 Element 插入到 Element 树上指定的位置

这个方法仅仅会调用一次,将 Element 的状态从 initial 变为 active

代码也很简单:


@mustCallSuper
void mount(Element? parent, Object? newSlot) {
  ///省略代码...
  _parent = parent;
  _slot = newSlot;
  _lifecycleState = _ElementLifecycle.active;
  _depth = _parent != null ? _parent!.depth + 1 : 1;
  if (parent != null) {
    _owner = parent.owner;
  }
 ///省略代码...
}

上面的代码就是形成树的过程:

  • 指定自己的父节点 —— parent 是谁
  • 指定自己在父节点中的位置 —— slot 是什么
  • 自己的深度 —— depth 比父节点的深度 +1

两棵树关联

我们看到了 Element 树的形成过程,但是 Element 是怎么和 Widget 对应上的呢? Element 类中含有 _widget 字段,也就是给 _widget 字段赋值的地方,就是两棵树关联的起来的地方。

我们看到代码中赋值的地方有两个:

  • 构造方法
Element(Widget widget)
  : assert(widget != null),
    _widget = widget;
  • 更新方法 update
void update(covariant Widget newWidget) {
  ///... 省略代码
  _widget = newWidget;
}

所以我们可以知道构造 Element 的时候就已经和 Widget 关联起来了,形成的 Element 树,必然是和 Widget 树一一对应的。

我们以 StatelessWidget 为例,看关联过程,这样更加具体。

StatelessWidget 具化成 StatelessElement 的时候,framework 就会调用 inflateWidget

Element inflateWidget(Widget newWidget, Object? newSlot) {
  //省略代码...
  final Element newChild = newWidget.createElement();
  return newChild;

newWidget 是 StatelessWidget,所以就会走到 StatelessWidget 的 createElement 中

@override
StatelessElement createElement() => StatelessElement(this);

就是构造了 StatelessElement

StatelessElement(StatelessWidget widget) : super(widget);

构造方法最后调用了 Element 的构造方法

Element(Widget widget)
  : assert(widget != null),
    _widget = widget;

这样完成了关联过程。

小结

Element 是 Widget 的具化,Widget 和 Element是一一对应的,在构造 Element 的时候就已经完成了两棵树的关联,Element 的 mount 方法形成了 Element 树。

上面看到了 Element 树的形成和关联过程,但是我们并不没在 Element 中看到渲染相关的动作。 那么渲染相关的在哪里呢?这就是下面要说的 RenderObject

RenderObject 是什么

RenderObject 简介

上面我们看到 Widget 中有一类是渲染类型的 Widget —— RenderObjectWidget。和 Widget 相比,RenderObjectWidget 要求子类 必须创建 RenderObject。我们先看一下 RenderObject 的官方描述。

An object in the render tree.

The [RenderObject] class hierarchy is the core of the 
rendering library's reason for being.

[RenderObject]s have a [parent], and have a slot called 
[parentData] in which the parent [RenderObject] can store 
child-specific data, for example,the child position. The 
[RenderObject] class also implements the basic
layout and paint protocols.

从上面的描述来看,我们可以知道:

  • RenderObect 是渲染树上的一个个小节点,并且 RenderObject 是渲染体系的核心类。和 Widget、Element 相似,也是概念。它下面也有很多具化的子类。

  • RenderObject 有两个比较重要的属性:parent 和 parentData。parent 很好理解,就是父节点。而 parentData是父节点存储的 分配给子节点的信息。什么叫分配给子节点呢? 比如索引信息,位置信息等等。比如 Row给每个子节点都分配一个索引编号,你在第一个位置,你在第二个位置等等。

  • RenderObject 实现了最最基本的布局和绘制规则。但是它不规定子类的具体 实现,也没有具体定义坐标系统。比如子类有几个子节点、子节点是否按着卡笛儿坐标系摆放、子节点是否布局超出了边界等等。

知道了RenderObject是什么。那么Widget、Element和RenderObject是什么关联的呢?

RenderObject 树关联

首先来看三者的关系,如下图:

Flutter 必知必会系列——三颗树到底是什么

  • 顶层的 Widget 和 Element 是紧密的关联的,和RenderObject没有直接关联。只有渲染型 Widget,三者才会紧密关联。

  • Element是桥梁作用,持有 Widget 和 RenderObject。找到 Element 就找到了关键。

  • Widget 负责创建、更新RenderObject,但并不负责维护 RenderObject。

Widget 提供了创建的方法,所以哪里调用了创建哪里就是形成树的地方。 RenderObjectElement 维护了 RenderObject,所以赋值的地方就是关联的地方。

从上面的 Element 分析之中,我们猜测,大概率就是 mount 方法。

@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot); //第一处
  //... 省略代码
  _renderObject = widget.createRenderObject(this); //第二处
  attachRenderObject(newSlot); //第三处
}

第一处 代码的super的 mount 完成了 Widget 和 Element 的关联

第二处 代码的赋值完成了节点关联

第三处 attach 完成了树的形成

_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);

就是一个个的插入子节点

到这里三棵树就彻底形成了:

Flutter 必知必会系列——三颗树到底是什么

每一个 Widget 都会有一个 Element 相对应,但是只有渲染类型的 Widget 才会有 RenderObject。所以官方文档有一句话 trictly speaking, the RenderObject tree is a subset of the Element tree,RenderObject 树是 Element 树的子集。

总结

总的来说,三棵树形成和关联就介绍完了,每棵树都各司其职。文档中说 Reusing elements is important for performance,下一篇我们就介绍 Reusing —— 复用 机制。

除了代码注释的文档之外,还有官方的文档。我翻译了文档中比较重要的两篇,想看中文的的可以看下面:

转载自:https://juejin.cn/post/7057356671948947464
评论
请登录