美文网首页
Flutter系列四:你真的了解StatelessWidget和

Flutter系列四:你真的了解StatelessWidget和

作者: chonglingliu | 来源:发表于2021-03-21 10:39 被阅读0次

    开发者在进行Flutter开发时,大部分工作基本上少不了与StatelessWidgetStatefulWidget打交道。大家是否真的了解StatelessWidgetStatefulWidget?

    讨论

    我阅读了很多网上的文章,大部分会讲解两者的使用上的区别,一部分文章有解释这两者的区别。但是他们的解释有的是字面解释,有的是浅尝辄止,有的甚至是有一定的误导。

    列出网上一些文章中的解释:

    1. 如果我们的Widget是StatelessWidget,那么当他的内容被创建出来之后,就不能再改变了。相反StatefulWidget就可以。
    2. 无状态Widget,就是说一旦这个Widget创建完成,状态就不允许再变动。有状态Widget,就是说当前Widget创建完成之后,还可以对当前Widget做更改,可以通过setState函数来刷新当前Widget来达到有状态。
    3. StatelessWidget是一个不需要状态更改的widget,它没有要管理的内部状态。StatefulWidget是可变状态的widget。

    如果你对上述一些观点很认同的话,我觉得阅读本篇文章应该可以给你提供一个不一样的理解视角。

    Widget

    我们要比较StatelessWidgetStatefulWidget的区别,我们得先知道什么是Widget

    官方对Widget的解释是:

    A widget is an immutable description of a part of a user interface.

    Widget是部分界面的不可变的描述信息

    重要的事情说三遍:

    Widget不可变的;

    Widget不可变的;

    Widget不可变的。

    我们从代码上看看Widget如何实现的不可变。Widget的代码如下:

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

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

    接下来我们再看看StatelessWidgetStatefulWidget的官方解释和相关代码:

    StatelessWidget --- 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);
    }
    

    StatefulWidget --- 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();
    }
    
    Widget总结
    1. StatelessWidgetStatefulWidget没有本质区别,他们的所有属性都是不可变的。它们都没法更新,除非用一个新的Widget去替换它们。
    2. StatefulWidget拥有一个可变的State

    这样我们就得到了一个结论:StatelessWidgetStatefulWidget的区别就在这个可变的State了。

    新的问题又来了,这个State扮演了什么作用呢?

    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);
    }
    
    1. _element把自己的_dirty属性设置为true;
    2. BuildOwner调用scheduleBuildFor方法。
    void scheduleBuildFor(Element element) {
        if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
          _scheduledFlushDirtyElements = true;
          // 1
          onBuildScheduled!();
        }
        _dirtyElements.add(element);
        element._inDirtyList = true;
      }
    
    1. BuildOwner调用onBuildScheduled方法;

    内容回顾:onBuildScheduled方法是在WidgetsBindinginitInstances中初始化的,一系列调用后最后调用的就是scheduleFrame请求Native Platform要刷新界面。

    1. element加入到dirtyElements中。

    在合适的时候Flutter Engine会回调SchedulerBindinghandleDrawFrame方法,最后会调用BuildOwnerbuildScope方法。

    void buildScope(Element context, [ VoidCallback? callback ]) {
        int dirtyCount = _dirtyElements.length;
        int index = 0;
        while (index < dirtyCount) {
            _dirtyElements[index].rebuild();
        }
    }
    

    遍历dirtyElements元素,每个element调用rebuild

    rebuild的作用是什么?没错,就是我们开头提到的对界面元素进行更新的操作。

    刷新渲染的具体逻辑,我将会在后续文章中详细介绍,这里没法详细展开。

    结论

    StatelessWidgetStatefulWidget的本质区别就是能否自我重新构建(self rebuild)。

    区别

    一些思考

    • 既然StatefulWidget的主要作用只是为了赋予了其自我重新构建(self rebuild)的能力,那为什么需要State呢?

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

    • 既然StatefulWidget的功能更完善,为什么又提供一个StatelessWidget呢?

    这个问题其实等同于为什么官方要限制我们使用self rebuild?每次Build都需要新建和销毁大量的WidgetElement Treediff,甚至繁重的渲染和重绘。官方推荐使用StatelessWidget,其实就是为了性能的考虑而对开发者进行的一些约束,限制开发者无节制的使用self rebuild造成的性能降低。

    • 可不可以在开发中全部都使用StatefulWidget

    当然可以,但是不推荐,理由见上个问题。

    • 可不可以在开发中全部都使用StatelessWidget

    如果是显示简单的不变的内容可以这样使用,但是这种场景太少了。至少在App应用中不太可能。

    • 开发中如何选择StatelessWidget还是StatefulWidget

    首选StatelessWidget,当无法满足需求的时候用VS Code或者Android Stutio的快捷键将其变成StatefulWidget

    实战分享

    我们前面比较了StatelessWidgetStatefulWidget的区别,进行了一些分析,到底如何写出更好更优化的代码,现在我们就用Flutter官方的计数器Demo来练练手。

    Counter

    通过前面的分析,我们知道点击FloatingActionButton会调用_MyHomePageStatesetState进行rebuild。如下图所示:

    demo_build

    细心的你可能发现问题了,我只是想修改Scaffold->Body->Center->Column->第二个Text中的文字。而Build的起点是Scaffold,这么长的构建链条相当于修改一个文字,把整个页面都重新构建了一次。就显然是一个无法忽视的问题。

    注意:真实的Build链条不是我上面列的这么短,因为Scaffold等都进行了封装,真实的Build得进入他们的build方法去了解,真实的Build链条比我们代码中看到的Scaffold->Body->Center->Column->第二个Text这个逻辑复杂多了。

    修改的思路就是我们只需要在第二个Text上封装一个StatefulWidget,让这个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的使用:

      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),
          ), 
        );
      }
    
    }
    

    这样就改造完成了。

    总结:

    我们需要对StatelessWidgetStatefulWidget有一个全面的了解,才能正确的使用他们。欢迎一起探讨和学习。

    相关文章

      网友评论

          本文标题:Flutter系列四:你真的了解StatelessWidget和

          本文链接:https://www.haomeiwen.com/subject/cqsncltx.html