注册

跟我学flutter:细细品Widget(二)StatelessWidget&StatefulWidget

前言


跟我学flutter系列:
跟我学flutter:我们来举个例子通俗易懂讲解dart 中的 mixin
跟我学flutter:我们来举个例子通俗易懂讲解异步(一)ioslate
跟我学flutter:我们来举个例子通俗易懂讲解异步(二)ioslate循环机制

跟我学flutter:在国内如何发布自己的Plugin 或者 Package

跟我学flutter:Flutter雷达图表(一)如何使用kg_charts

跟我学flutter:细细品Widget(一)Widget&Element初识

跟我学flutter:细细品Widget(二)StatelessWidget&StatefulWidget

跟我学flutter:细细品Widget(三)ProxyWidget,InheritedWidget

跟我学flutter:细细品Widget(四)Widget 渲染过程 与 RenderObjectWidget

跟我学flutter:细细品Widget(五)Element

企业级篇目:
跟我学企业级flutter项目:用bloc手把手教你搭建用户认证系统
跟我学企业级flutter项目:dio网络框架增加公共请求参数&header
跟我学企业级flutter项目:如何用dio封装一套企业级可扩展高效的网络层
跟我学企业级flutter项目:如何封装一套易用,可扩展的Hybrid混合开发webview
跟我学企业级flutter项目:手把手教你制作一款低耦合空页面widget

StatelessWidget和StatefulWidget是Flutter开发必不可少的。两者的区别在于state。有状态的widget需要继承StatefulWidget无状态的需要继承StatelessWidget。


StatelessWidget


无状态Widget


源码


abstract class StatelessWidget extends Widget {

const StatelessWidget({ Key? key }) : super(key: key);

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

@protected
Widget build(BuildContext context);
}

createElement


StatelessWidget的对应Element 为StatelessElement


build


用于构建widget
build 在很多种情况下会被调用



  1. Widget第一次被加载(Widget 第一次被加入到 Widget Tree 中 ,更准确地说是其对应的 Element 被加入到 Element Tree 时,即 Element 被挂载(mount)时)
  2. Parent Widget 修改了其配置信息;
  3. 该 Widget 依赖的Inherited Widget发生变化时。


当Parent Widget或 依赖的Inherited Widget频繁变化时,build方法也会频繁被调用。因此,提升build方法的性能就显得十分重要,Flutter 官方给出了几点建议:



1.减少不必要的中间节点,即减少 UI 的层级,*如:对于「Single Child Widget」,没必要通过组合「Row」、「Column」、「Padding」、「SizedBox」等复杂的 Widget 达到某种布局的目标,或许通过简单的「Align」、「CustomSingleChildLayout」即可实现。又或者,为了实现某种复杂精细的 UI 效果,不一定要通过组合多个「Container」,再附加「Decoration」来实现,通过 「CustomPaint」自定义或许是更好的选择;
2.尽可能使用const Widget,*为 Widget 提供const构造方法;
3.可以将「Stateless Widget」重构成「Stateful Widget」,*以便可以使用「Stateful Widget」中一些特定的优化手法,如:缓存「sub trees」的公共部分,并在改变树结构时使用GlobalKey;
4.尽量减小 rebuilt 范围,*如:某个 Widget 因使用了「Inherited Widget」,导致频繁 rebuilt,可以将真正依赖「Inherited Widget」的部分提取出来,封装成更小的独立 Widget,并尽量将该独立 Widget 推向树的叶子节点,以便减小 rebuilt 时受影响的范围。


StatefulWidget


有状态 Widget



StatefulWidget本身是不可变,状态在State中。



源码


abstract class StatefulWidget extends Widget {

const StatefulWidget({ Key? key }) : super(key: key);

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

@protected
@factory
State createState();
}

createElement


StatefulElement的对应Element 为StatefulElement


createState



实际上是「Stateful Widget」对应的「Stateful Element」被添加到 Element Tree 时,伴随「Stateful Element」的初始化,createState方法被调用。从后文可知一个 Widget 实例可以对应多个 Element 实例 (也就是同一份配置信息 (Widget) 可以在 Element Tree 上不同位置配置多个 Element 节点),因此,createState方法在「Stateful Widget」生命周期内可能会被调用多次。



另外,需要注意的是配有GlobalKey的 Widget 对应的 Element 在整个 Element Tree 中只有一个实例。


State


生命周期:


在这里插入图片描述



  1. 在对应的(Stateful Element)被挂载 (mount) 到树上时,通过StatefulElement.constructor –> StatefulWidget.createState创建 State 实例


State._emelent就是对应的Element 实例,State与Element绑定关系一经确定,在整个生命周期内不会再变了 (Element 对应的 Widget 可能会变,但对应的 State 永远不会变),期间,Element可以在树上移动,但上述关系不会变





  1. StatefulElement 在挂载过程中接着会调用State.initState,子类可以重写该方法执行相关的初始化操作 (此时可以引用context、widget属性);




  2. 同样在挂载过程中会调用State.didChangeDependencies,该方法在 State 依赖的对象 (如:「Inherited Widget」) 状态发生变化时也会被调用,*子类很少需要重写该方法,*除非有非常耗时不宜在build中进行的操作,因为在依赖有变化时build方法也会被调用;




  3. State 初始化已完成,其build方法此后可能会被多次调用,在状态变化时 State 可通过setState方法来触发其子树的重建;




  4. 此时,「element tree」、「renderobject tree」、「layer tree」已构建完成,完整的 UI 应该已呈现出来。此后因为变化,「element tree」中「parent element」可能会对树上该位置的节点用新配置 (Widget) 进行重建,当新老配置 (oldWidget、newWidget)具有相同的「runtimeType」&&「key」时,framework 会用 newWidget 替换 oldWidget,并触发一系列的更新操作 (在子树上递归进行)。同时,State.didUpdateWidget方法被调用,子类重写该方法去响应 Widget 的变化;




  5. 在 UI 更新过程中,任何节点都有被移除的可能,State 也会随之移除,(如上一步中「runtimeType」||「key」不相等时)。此时会调用State.deactivate方法,由于被移除的节点可能会被重新插入树中某个新的位置上,故子类重写该方法以清理与节点位置相关的信息 (如:该 State 对其他 element 的引用)、同时,不应在该方法中做资源清理;




重新插入操作必须在当前帧动画结束之前



  1. 当节点被重新插入树中时,State.build方法被再次调用;
  2. 对于在当前帧动画结束时尚未被重新插入的节点,State.dispose方法被执行,State 生命周期随之结束,此后再调用State.setState方法将报错。子类重写该方法以释放任何占用的资源。

请添加图片描述


源码


void setState(VoidCallback fn) {
assert
(fn != null);
assert
(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts([
ErrorSummary('setState() called after dispose(): $this'),
ErrorDescription(
'This error happens if you call setState() on a State object 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 '
'setState() 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 setState() to ensure the object is still in the '
'tree.',
),
ErrorHint(
'This error might indicate a memory leak if setState() 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().',
),
]);
}
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
throw FlutterError.fromParts([
ErrorSummary('setState() called in constructor: $this'),
ErrorHint(
'This happens when you call setState() on a State object for a widget that '
"hasn't been inserted into the widget tree yet. It is not necessary to call "
'setState() in the constructor, since the state is already assumed to be dirty '
'when it is initially created.',
),
]);
}
return true;
}());
final Object? result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError.fromParts([
ErrorSummary('setState() callback argument returned a Future.'),
ErrorDescription(
'The setState() method on $this was called with a closure or method that '
'returned a Future. Maybe it is marked as "async".',
),
ErrorHint(
'Instead of performing asynchronous work inside a call to setState(), first '
'execute the work (without updating the widget state), and then synchronously '
'update the state inside a call to setState().',
),
]);
}
// We ignore other types of return values so that you can do things like:
// setState(() => x = 3);
return true;
}());
_element!.markNeedsBuild();
}

分析:



  1. _debugLifecycleState == _StateLifecycle.defunct 在State.dispose后不能调用setState
  2. _debugLifecycleState == _StateLifecycle.created && !mounted 在 State 的构造方法中不能调用setState
  3. if (result is Future) setState方法的回调函数 (fn) 不能是异步的 (返回值为Future)


通过setState方法之所以能更新 UI,是在其内部调用_element.markNeedsBuild()




若State.build方法依赖了自身状态会变化的对象,如:ChangeNotifier、Stream或其他可以被订阅的对象,需要确保在initState、didUpdateWidget、dispose



等 3 方法间有正确的订阅 (subscribe) 与取消订阅 (unsubscribe) 的操作:
1.在initState中执行 subscribe;
2.如果关联的「Stateful Widget」与订阅有关,在didUpdateWidget中先取消旧的订阅,再执行新的订阅;
3.在dispose中执行 unsubscribe。



在State.initState方法中不能调用BuildContext.dependOnInheritedWidgetOfExactType,但State.didChangeDependencies会随之执行,在该方法中可以调用。



文章内容参考:http://www.jb51.net/article/221…


0 个评论

要回复文章请先登录注册