Flutter学习记录 - Widget、State、Context
前言
如何学习使用一门工具进行开发?
对于这个问题,笔者认为应该从概念开始,了解概念后才更好去理解其原理,而只有在理解原理之后你才敢说会使用了。
类似Jetpack Compose,如果不理解State、可重组函数、重组作用域这些概念,又从何挖其原理呢?又如何去考虑避免或减少重组触发写出准确的代码呢?
笔者个人认为会使用,并不只是能够运用其出预期结果,而是会根据自己的理解使用正确/准确的方式实现预期结果。
Flutter是一个跨平台UI构架工具,其中的 Widget、State和Context 是每一个Flutter开发者都需要充分理解的重要概念之一,特别是对于初学者。
笔者作为一个Flutter初学者,本文将尝试解释这些概念,加深理解。
Widget
Widget的中文意思是小部件,用户所看到的界面基本都是Widget,当然也可能是Platform View(平台视图)。
有这么一句话:在flutter中,一切都是Widget。其实在flutter中,每个widget专注的功能很小,比如padding/alignment,在其他语言中它是一个修饰的属性,但是在flutter中,它却是一个widget。这样做的好处就是我们开发者可以通过这些基础的widgets任意组合,从而构建出复杂,个性化的大widget。但是这样的缺点也很明显,就是套层。跟套娃一样,组合多了,层级也更多,代码看起来就不那么舒服了。
以下是常见Widget Container
中的build方法
@override
Widget build(BuildContext context) {
Widget? current = child;
if (child == null && (constraints == null || !constraints!.isTight)) {
current = LimitedBox(
maxWidth: 0.0,
maxHeight: 0.0,
child: ConstrainedBox(constraints: const BoxConstraints.expand()),
);
} else if (alignment != null) {
current = Align(alignment: alignment!, child: current);
}
final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null) {
current = Padding(padding: effectivePadding, child: current);
}
if (color != null) {
current = ColoredBox(color: color!, child: current);
}
if (clipBehavior != Clip.none) {
assert(decoration != null);
current = ClipPath(
clipper: _DecorationClipper(
textDirection: Directionality.maybeOf(context),
decoration: decoration!,
),
clipBehavior: clipBehavior,
child: current,
);
}
if (decoration != null) {
current = DecoratedBox(decoration: decoration!, child: current);
}
if (foregroundDecoration != null) {
current = DecoratedBox(
decoration: foregroundDecoration!,
position: DecorationPosition.foreground,
child: current,
);
}
if (constraints != null) {
current = ConstrainedBox(constraints: constraints!, child: current);
}
if (margin != null) {
current = Padding(padding: margin!, child: current);
}
if (transform != null) {
current = Transform(transform: transform!, alignment: transformAlignment, child: current);
}
return current!;
}
通过代码可以看到,current这个widget通过一步一步根据使用传递的参数进行判断从而嵌入到不同的widget组合而成。
StatelessWidget & StatefulWidget
根据Widget是否包含状态,Widget又分为StatelessWidget
和StatefulWidget
两种Widget。
StatelessWidget无状态小部件,是不会随着绑定的值的改变而动态改变的。所以StatelessWidget的生命周期很简单: 初始化 –> 通过build方法渲染。
StatefulWidget有状态小部件,和StatelessWidget刚好相反,它需要绑定一个状态,且build方法在State中。当这个状态在改变的时候,将强制重建Widget。
StatefulWidget的生命周期如下:
stateDiagram [*] --> createState createState --> initState initState --> didChangeDependencies didChangeDependencies --> build build --> addPostFrameCallback addPostFrameCallback --> didUpdateWidget didUpdateWidget --> deactivate deactivate --> dispose dispose --> [*]
State
前面我们了解了Widget,在flutter中,Widget的构建是基于State的,所以State是数据,是用来渲染widget的数据。
在flutter应用中,State又分为Ephemeral State
和App State
。
Ephemeral State
Ephemeral State是局部状态,一般作用于某个或某些Widget,在开发的过程中,我们可以针对某个页面/页面部分内容进行整理和定义Ephemeral State,而不需要使用状态管理技术来处理这些状态,使用setState()
方法实现目的即可。
App State
App State是全局状态,它作用于整个App范围。相对于Ephemeral State,App State处理情况会更复杂点,它可以运用于整个app中任意时间的任意界面,而不是对某个或某些特定的Widget来管理。所以这里我们需要用到状态管理技术。
了解更多: List of state management approaches
Context
这里所谓的Context实际指的是BuildContext,每个Widget都有一个build方法,这个方法就有一个参数便是BuildContext。上文提到过,flutter的Widget专注于更小的功能,即使一个简单的Container也是由多个Widget组合而成,那么其组合之间的关系是通过什么管理的呢? 任何的Widget都可重用到其他地方,所以它在Widget组合树中,Widget不会保留任何信息。在Widget抽象类中有这么个方法createElement
,其注释Inflates this configuration to a concrete instance.
翻译过来就是将配置扩展为具体实例。
另外在flutter源码中,BuildContext上也有这么一句注释
[BuildContext] objects are actually [Element] objects. The [BuildContext] interface is used to discourage direct manipulation of [Element] objects.
所以Widget和Context之间的关系是一对一的,而维护Widget树的关联信息在Element中,而Context则是为了避免直接操作Element。
总结
- Widget是flutter UI的基本,分为
StatelessWidget
和StatefulWidget
,如果一个Widget在其生命周期中考虑变更且变更后将强制重建,这时候使用StatefulWidget,否则使用StatelessWidget。 - State是Widget的数据信息,分为
Ephemeral State
和App State
,Ephemeral State
多数用于StatefulWidget并在其内部管理,用setState
方法实现更新。App State
一般都通过状态管理技术进行管理。 - BuildContext和Widget存在一对一的关系,每个Widget都有一个Context。Context实际上是Element object,在构建一个widget时,会调用widget的createElement方法创建Element,而element对象中又存在parent,children,size等信息,从而构成element tree。