flutter-context
BuildContext
每次我们在编写界面部分代码的时候,都是在build函数中进行操作。而build函数则需要默认传入一个BuildContext。我们来看看这到底是啥。
1 | abstract class BuildContext { |
我们可以看到BuildContext其实是一个抽象类,但是每次build函数传进来的是什么呢。我们来看看构建视图的时候到底发生了什么。
Flutter如何构建视图
在Flutter中,Everything is Widget,我们通过构造函数嵌套Widget来编写UI界面。实际上,Widget并不是真正要显示在屏幕上的东西,只是一个配置信息,它永远是immutable(不可变)的,并且可以在多处重复使用。那真正显示在屏幕上的视图树是什么呢?Element Tree!
那我们来看一下,在构建视图的时候究竟发生了什么。这里以StatelessWidget为例。
1 | abstract class StatelessWidget extends Widget { |
当要把这个widget装进视图树的时候,首先会去createElement,并将当前widget传给Element。
我们再来看一看这个StatelessElement是什么
1 | class StatelessElement extends ComponentElement { |
我们可以看到,通过将widget传入StatelessElement的构造函数,StatelessElement保留了widget的引用,并且将会调用build方法。
而这个build方法真正调用的则是widget的build方法,并将this,也就是该StatelessElement对象传入。我们知道,build方法需要传入的是一个BuildContext,为什么传进去了StatelessElement?于是我们继续看。
1 | class StatelessElement extends ComponentElement |
实际上是Element类实现了BuildContext,并由ComponentElement -> StatelessElement 继承。
所以我们现在再来看官方对于BuildContext的解释:
BuildContext objects are actually Element objects. The BuildContext interface is used to discourage direct manipulation of Element objects.
BuildContext对象实际上就是Element对象,BuildContext 接口用于阻止对 Element 对象的直接操作。
我们现在终于知道这个BuildContext是哪里来的了。让我们再来梳理一下,flutter构建视图究竟做了什么。
视图树装载过程
StatelessWidget
- 首先它会调用StatelessWidget的 createElement 方法,并根据这个widget生成StatelesseElement对象。
- 将这个StatelesseElement对象挂载到Element树上。
- StatelesseElement对象调用widget的build方法,并将element自身作为BuildContext传入。
StatefulWidget
- 首先同样也是调用StatefulWidget的 createElement方法,并根据这个widget生成StatefulElement对象,并保留widget引用。
- 将这个StatefulElement挂载到Element树上。
- 根据widget的 createState 方法创建State。
- StatefulElement对象调用state的build方法,并将element自身作为BuildContext传入。
所以我们在build函数中所使用的context,正是当前widget所创建的Element对象。
What do you need to know about context
- Context is a link to the location of a widget in the tree structure of widgets.
- Context can belong to only one widget.
- If a widget has child widgets, then the context of the parent widget becomes the parent context for the contexts of direct child elements.
- A widget is visible only in its own context or in the context of its parent context.
Thus, it becomes clear that knowing the children’s context, you can easily find the parent widget. Conversely, using the parent context you can find the child widget.
如果你还记得这幅图:
of(context)方法
在flutter中我们经常会使用到这样的代码
1 | //打开一个新的页面 |
那么这个of(context)到底是个什么呢。我们这里以Navigator打开新页面为例。
1 | static NavigatorState of( |
可以看到,关键代码部分通过context.rootAncestorStateOfType向上遍历 Element tree,并找到最近匹配的 NavigatorState。也就是说of实际上是对context跨组件获取数据的一个封装。
而我们的Navigator的 push操作就是通过找到的 NavigatorState 来完成的。
不仅如此,BuildContext还有许多方法可以跨组件获取对象
1 | ancestorInheritedElementForWidgetOfExactType(Type targetType) → InheritedElement |
需要注意的是,如果我们需要与祖先 Inherit 对象建立长期联系,dependOnInheritedWidgetOfExactType
系列的方法不能在 initState
中调用,为了确保 Widget 在 Inherit 值更改时正确更新自身,请在 State.didChangeDependencies
阶段调用 of
方法。
当然,这里的讲解只是非常简略的,如果你想更深入的了解BuildContext
,可以参考这篇博客,这篇博客由浅入深地解释了对应的机制。
我在这里摘了一点:
So now we know about two categories of widgets:
- Composing widgets. These extend either
StatelessWidget
orStatefulWidget
. They don’t do anything related to rendering but just compose more complex views out of other widgets. They override thebuild
method.- Rendering widgets. These ultimately extend
RenderObjectWidget
but typically through one of the following subclasses:
SingleChildRenderObjectWidget
for widgets that paint only one child, likeColoredBox
MultiChildRenderObjectWidget
for widgets that paint multiple children, likeColumn
LeafChildRenderObjectWidget
for widgets that don’t paint any children, only themselves, likeErrorWidget
- Those widgets do the actual lower-level rendering via the
RenderObject
class. They don’t override thebuild
method but rathercreateRenderObject
andupdateRenderObject
.During the development of your Flutter apps, you will usually extend the
Stateless
andStateful
widgets. But it’s important to understand that no matter how aggressively you compose your widget tree, it will always end up with some kind ofRenderObjectWidget
s.
在了解了Widget
与RenderObject
后,或者说,widget tree and the render object tree
,我们引出 Element
:
Elements are what glue together the immutable widgets and mutable render objects. The elements are mutable themselves, meaning that their lifecycle is much longer than a widget’s. We’ll cover this more in a bit. Side note: We will be simplifying and skipping a few things on purpose because not all the implementation details are necessary to understand this article.
The very first time that your
Widget
gets created and inserted into the widget tree, it also creates anElement
via itscreateElement
method. But the next time the tree gets rebuilt, the framework checks if the widget that gets returned from thebuild
method can update the element in that place in the tree.
…
Now, let’s switch our focus back to our
RenderObjectWidget
for a moment. In the same way theStatelessWidget
gets inflated into aStatelessElement
, theRenderObjectWidget
gets inflated into aRenderObjectElement
. But besides updating the link to itswidget
, it also updates itsrenderObject
. But theRenderObject
s are also smart, as they check if there were any changes in the configuration. If there weren’t, then they don’t repaint. If there were, then they repaint those parts.
参考
https://juejin.cn/post/6844903777565147150
https://appvesto.medium.com/flutter-what-is-context-ef2ed6dc7162