frc 3465019fc8ab288d5fc1a6e70f381775 - [Flutter开发]04.Flutter中State的生命周期

这篇文章来总结下Flutter中有状态和无状态的widget,以及状态的生命周期。

Widget与Element

在Flutter中,Widget的功能是描述显示元素的一个配置数据,而Element是表示最终绘制在设备屏幕上的显示元素,所以Widget描述的是Element的配置数据。

Widget类的声明如下:

@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;

  @protected
  Element createElement();

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

我们可以看到声明中有createElement()方法,作用就是生成对应节点的Element对象,而且一个Widget可以对应多个Element。

一般我们创建一个新组件是继承StatelessWidget或StatefulWidget,他们又间接继承Widget。

StatefulWidget

StatefulWidget的类定义:

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);

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

  @protected
  State createState();
}

可以看到StatefulWidget定义中还多了个createState()方法,它是用于创建和Stateful widget相关的状态,它在Stateful widget的生命周期中可能会被多次调用。例如,当一个Stateful widget同时插入到widget树的多个位置时,该方法就会被调用,为每一个位置生成一个独立的State实例,本质上一个StatefulElement对应一个State实例。

总结:widget与Element是一对多的关系,而State对象和StatefulElement是一对一关系。

接下来,就来了解一下State的生命周期。

State的生命周期

State表示与其对应的StatefulWidget要维护的状态,当它被改变时,我们通过手动调用它的setState()方法通知Flutter framework状态发生改变,而Flutter framework在收到消息后,会重新调用build方法重构widget树,从而更新UI。

它有两个常用属性:

  1. widget,与该State实例关联的widget实例,该实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。

  2. context,它是BuildContext类的一个实例,表示当前widget在widget树中的上下文,每一个widget都会对应一个context对象(因为每一个widget都是widget树上的一个节点)。实际上,context是当前widget在widget树中位置中执行”相关操作“的一个句柄,比如它提供了从当前widget开始向上遍历widget树以及按照widget类型查找父级widget的方法。下面是在子树中获取父级widget的一个示例:

class ContextRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Context测试"),
      ),
      body: Container(
        child: Builder(builder: (context) {
          // 在Widget树中向上查找最近的父级`Scaffold` widget
          Scaffold scaffold = context.ancestorWidgetOfExactType(Scaffold);
          // 直接返回 AppBar的title, 此处实际上是Text("Context测试")
          return (scaffold.appBar as AppBar).title;
        }),
      ),
    );
  }
}

StatefulWidget生命周期如下图:

frc 26c4a5f02a62cfaef9b2ce40179dabbc - [Flutter开发]04.Flutter中State的生命周期
  • initState:当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。

  • didChangeDependencies:在之前build() 中包含了一个InheritedWidget,然后在之后的build() 中InheritedWidget中的数据发生了变化,如果InheritedWidget的子widget使用了父widget中InheritedWidget的数据而依赖了InheritedWidget的话,那didChangeDependencies()回调就会被调用。一般来说,子widget很少会重写此方法,因为在依赖改变后framework也都会调用build()方法。但是,如果你需要在依赖改变后执行一些昂贵的操作,比如网络请求,那最好在此方法中执行,这样可以避免每次build()都执行这些昂贵操作。

  • reassemble():此回调是专门为了开发调试而提供的,在热重载时会被调用,而在Release模式下永远不会被调用。

  • didUpdateWidget():在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。从上面的Widget类的声明看到,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。

  • deactivate():当State对象从树中被移除时调用。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。

  • dispose():当State对象从树中被永久移除时调用,通常在此回调中释放资源。

[Flutter开发]04.Flutter中State的生命周期