Flutter

Flutter 中的 Widget 和 State

2023-07-20  本文已影响0人  大成小栈

在进行 Flutter 开发时少不了与 StatelessWidget 和 StatefulWidget 打交道,在比较 StatelessWidget 和 StatefulWidget 的区别之前,需要先知道什么是 Widget 。

1. Widget

官方对 Widget 的解释是:
A widget is an immutable description of a part of a user interface.
即 Widget是部分界面的不可变的描述信息 。

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

// 可以看到 Widget 左上角的`@immutable`注解(意思是所有的属性必须是 final 修饰),
// 也就是 Widget 一旦初始化以后,其属性将不可变。

--->> A widget that does not require mutable state.

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

  @override  
  StatelessElement createElement() => StatelessElement(this);   
  
  @protected  
  Widget build(BuildContext context);
}

--->> A widget that has mutable state.

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

  @override  
  StatefulElement createElement() => StatefulElement(this);   
  
  @protected  
  @factory  
  State createState();
}

StatelessWidget 和 StatefulWidget 内部所有属性都是不可变的;
两者都没法自我更新,除非用一个新的Widget去替换它们;
StatefulWidget 有个State可变属性,StatelessWidget没有。

StatelessWidget:无状态组件的生命周期只有 build 构建这个过程;
StatefulWidget:无状态组件的生命周期指的是 State 的生命周期。

2. State

我们进行界面的修改,一般会调用state.setState()方法:

void setState(VoidCallback fn) {    
  final dynamic result = fn() as dynamic;    
  _element!.markNeedsBuild();  
}

// setState方法:
// 1)执行传入的函数;
// 2)_element 调用了 markNeedsBuild 方法;

void markNeedsBuild() {    
  if (dirty)  return;    

  _dirty = true;    
  owner!.scheduleBuildFor(this);
}

// markNeedsBuild方法:
// _element 把自己的 _dirty 属性设置为true;
// BuildOwner 调用 scheduleBuildFor 方法;

void scheduleBuildFor(Element element) {    
  if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {      
    _scheduledFlushDirtyElements = true;      
    
    // BuildOwner 调用 onBuildScheduled 方法
    onBuildScheduled!();    
  }   

  // 将element加入到dirtyElements中
  _dirtyElements.add(element);    
  element._inDirtyList = true;  
}

onBuildScheduled 方法是在 WidgetsBinding 的 initInstances 中初始化的,一系列调用后最后调用的就是 scheduleFrame,请求 Native Platform 要刷新界面。

在合适的时候 Flutter Engine 会回调 SchedulerBinding 的handleDrawFrame方法,最后会调用 BuildOwner 的buildScope方法。

void buildScope(Element context, [ VoidCallback? callback ]) {    
  int dirtyCount = _dirtyElements.length;    
  int index = 0;    
  while (index < dirtyCount) {
    // 遍历 dirtyElements,每个 element 调用rebuild
    _dirtyElements[index].rebuild();    
  }
}

// 其中,rebuild 的作用就是对界面元素进行更新的操作

结论:StatelessWidget 和 StatefulWidget 的本质区别就是能否自我重新构建(self rebuild)

3. State存在的意义

Widget 依赖于构造函数Build方法中的BuildContext中的外部信息,如果是外部触发的Build(例如:祖先Widget build),所有信息都是完整的。如果self rebuild则无法获取更新后的外部信息,所以需要内部维护一份不依赖于外部的信息,State就是这个作用。

为什么额外提供了一个StatelessWidget呢?
答:StatefulWidget 每次 Build 都需要新建和销毁大量的 Widget ,Element Tree 的 diff ,甚至繁重的渲染和重绘。StatelessWidget可限制开发者无节制的使用 self rebuild 造成的性能降低。

示例:

了解了 StatelessWidget 和 StatefulWidget 的区别,我们就可以对 Flutter 官方的计数器Demo进行优化。
通过前面的分析,我们知道点击FloatingActionButton会调用_MyHomePageStatesetState进行rebuild。如下图所示:

问题:代码的目的只是想修改第二个Text,而Build的起点是ScaffoldScaffold->Body->Center->Column->第二个Text中的文字。

这么长的构建链条只修改了一个文字,就把整个页面都重新构建了一次。就显然是一个无法忽视的问题。注意:真实的Build链条不是我上面列的这么短,因为Scaffold等都进行了封装,真实的Build得进入他们的build方法去了解。

修改的思路就是需要新封装一个StatefulWidget来替换第二个Text,让这个StatefulWidgetsetState去触发第二个Text的文字修改。

//// 新建一个 CounterText
class CounterText extends StatefulWidget {   
  final _CounterTextState state = _CounterTextState();   

  CounterText({    Key key,  }) : super(key: key);   

  @override  
  _CounterTextState createState() => state;
} 

class _CounterTextState extends State<CounterText> {   
  int _counter = 0;  

  void _incrementCounter() {    
    setState(() {      
    _counter++;    
   });  
  }    

  @override  
  Widget build(BuildContext context) {    
    return Container(      
              child: Text(        
                    '$_counter',        
                    style: Theme.of(context).textTheme.headline4,      
              ),    
    );  
  }
}

//// CounterText 的使用:
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
 
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
 
class _MyHomePageState extends State<MyHomePage> {
  CounterText counterText = CounterText();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            counterText,
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counterText.state._incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }
}

这样就改造完成了。

4. State的生命周期

StatefulWidget 持有 State 能进行 self Build,以更新界面;
State也是有生命周期的,并有回调接口。

Flutter的启动流程分析可知,Widget对应的Element插入Element Tree这一个过程是在inflateWidget方法中实现的:

Element inflateWidget(Widget newWidget, dynamic newSlot) {
    final Key? key = newWidget.key;
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    return newChild;
  }

// `createElement()`是创建的一个**StatefulElement**对象:
StatefulElement createElement() => StatefulElement(this);

// `StatefulElement(this)`的逻辑
StatefulElement(StatefulWidget widget)
      : state = widget.createState(),
        super(widget) {
    state._element = this;
    state._widget = widget;
}
 
final State<StatefulWidget> state;

1)调用 widget.createState() 生成了一个 State 对象,StatefulElement对象持有这个 state;
2)state 又持有 StatefulElement 对象,它们存在循环引用关系;
3)state也持有了StatefulWidget对象。

三者的引用逻辑有点绕,用一个图表示如下:

注意:

  1. StatefulElement 不需要直接引用 StatefulWidget,因为它持有 State,从而间接持有 StatefulWidget。
  2. StatelessElement 是需要直接持有 StatelessWidget 的。

_element被赋值后,mounted 就变为 true 了。

bool get mounted => _element != null;

接上面的inflateWidget方法,新创建的element对象会调用mount方法。
由于StatefulElement没有重写mount方法,实际调用的是父类ComponentElementmount方法。

void mount(Element? parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _firstBuild();
}

StatefulElement 的 _firstBuild 方法中调用了 state.initState()

void _firstBuild() {
    try {
      // 1
      final dynamic debugCheckForReturnedFuture = state.initState() as dynamic;
    } finally {
    }
    state.didChangeDependencies();
    super._firstBuild();
}

从上面的代码,我们可以看到在_firstBuild中调用了didChangeDependencies方法,也就是说initStatedidChangeDependencies这两个方法在_firstBuild中是一起执行的。

我们看到performRebuild的时候,StatefulElement_didChangeDependencies变为true后,state也会调用didChangeDependencies方法。

void performRebuild() {
    if (_didChangeDependencies) {
      state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
}

InheritedWidget的值变化后,有可能会将_didChangeDependencies的值变为true。

我们看看super.performRebuild()方法:

<!-- ComponentElement -->
void performRebuild() {
    Widget? built;
    try {
      built = build();
    }finally {
 
    }
    _child = updateChild(_child, built, slot);
}
 
<!-- StatefulElement -->
Widget build() => state.build(this);

StatefulElementperformRebuild方法中调用了build方法,也就是state调用了build方法。

void update(StatefulWidget newWidget) {
    super.update(newWidget);
    final StatefulWidget oldWidget = state._widget!;
    _dirty = true;
    state._widget = widget as StatefulWidget;
    try {
      final dynamic debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
    } finally {
    }
    rebuild();
  }

element调用update时候,state会调用didUpdateWidget方法。 在父Widget引起重构Build的时候,如果element能够复用就会调用update方法。

该方法供开发者调用,只要是mounted为true,就能够进行Self rebuild

elementactive变为inactive的时候会调用statedeactivate方法。

void deactivate() {    state.deactivate();    super.deactivate();}

此时,elementElement tree中移除了,但是若果能被复用有可能会重新加到Element tree中。

dispose

elementinactive变为defunct的时候,会调用statedispose方法。

void unmount() {    super.unmount();    state.dispose();    state._element = null;}

由于执行了state._element = null;,所以这时候mounted就变为了falsestate将会被垃圾回收器回收。

总结:

  1. StatefulWidget实例化StatefulElement对象的时候会调用widget.createState()创建State对象,然后StateStatefulElement相互引用;
  2. State具有对StatefulElement的引用后,mounted就变为true, 此时就可以调用setState方法了;
  3. StatefulElement对象然后调用mount方法挂载到Element Tree上去。挂载后时会调用initStatedidChangeDependencies方法;
  4. 然后element会调用performRebuild方法,此时会调用Statebuild方法,如果InheritedWidget的值变化也会调用didChangeDependencies方法;
  5. 父Widget引起重构Build的时候,如果element能够复用就会调用StatedidUpdateWidget方法;
  6. elementactive变为inactive的时候会调用statedeactivate方法;
  7. elementinactive变为defunct的时候,会调用statedispose方法,并将_element置为null, 此时就不能再调用setState方法了;
  8. 等待垃圾回收器回收。

5. State生命周期示例

widget,它表示与该State实例关联的widget实例,由Flutter framework动态设置。
注意:这种关联并非永久的,因为在应用声明周期中,UI树上的某一个节点的widget实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。

context,它是BuildContext类的一个实例,表示构建widget的上下文,它是操作widget在树中位置的一个句柄,它包含了一些查找、遍历当前Widget树的一些方法。每一个widget都有一个自己的context对象。

class _CounterWidgetState extends State<CounterWidget> {  
  int _counter;

  @override
  void initState() {
    super.initState();
    //初始化状态  
    _counter=widget.initValue;
    print("initState");
  }

  @override
  Widget build(BuildContext context) {
    print("build");
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text('$_counter'),
          //点击后计数器自增
          onPressed:()=>setState(()=> ++_counter,
          ),
        ),
      ),
    );
  }

  @override
  void didUpdateWidget(CounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }

  @override
  void deactivate() {
    super.deactivate();
    print("deactive");
  }

  @override
  void dispose() {
    super.dispose();
    print("dispose");
  }

  @override
  void reassemble() {
    super.reassemble();
    print("reassemble");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }
}

initState:当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,因此通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。

不能在该回调中调用 BuildContext.inheritFromWidgetOfExactType(该方法用于在Widget树上获取离当前widget最近的一个父级 InheritFromWidget ,关于 InheritedWidget 我们将在后面章节介绍),原因是在初始化完成后,Widget 树中的 InheritFromWidget 也可能会发生变化,所以正确的做法应该在在 build() 方法或 didChangeDependencies() 中调用它。

didChangeDependencies():当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget,然后在之后的build() 中InheritedWidget发生了变化,那么此时InheritedWidget的子widget的didChangeDependencies()回调都会被调用。

典型的场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调。

build():此回调读者现在应该已经相当熟悉了,它主要是用于构建Widget子树的。

会在如下场景被调用:
1.在调用initState()之后。
2.在调用didUpdateWidget()之后。
3.在调用setState()之后。
4.在调用didChangeDependencies()之后。
5.在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。

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

didUpdateWidget():在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。

注:Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。

deactivate():当State对象从树中被移除时,会调用此回调。

在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。

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

参考文章:
https://www.jianshu.com/p/3beb8f2f2b02
https://blog.csdn.net/jdsjlzx/article/details/123853833

Widget生命周期,很好的文章
https://blog.csdn.net/jzman/article/details/112454883

上一篇 下一篇

猜你喜欢

热点阅读