Flutter基建 - 形影不离的State**Widget

本篇基于Flutter 3.16.4,Dart 3.2.3版本

Flutter 3.16.4 • channel stable • github.com/flutter/flu...

Framework • revision 2e9cb0aa71 (3 days ago) • 2023-12-11 14:35:13 -0700

Engine • revision 54a7145303

Tools • Dart 3.2.3 • DevTools 2.28.4

本篇为Flutter基建的第七篇文章,主要介绍Flutter中StatelessWidget和StatefulWidget使用以及基础类Widget相关知识,希望本篇文章可以帮助大家了解和熟悉Widget的基本知识,下面一起进入文章的内容吧~

Widget基类

在这之前如果你观察过StatelessWidget和StatefulWidget的源码就会发现,其实它俩都是继承自Widget抽象类。

scala 复制代码
abstract class StatelessWidget extends Widget

abstract class StatefulWidget extends Widget

二者的区别在于StatelessWidget表示的是一个无状态的Widget,它不可以更新Widget内部状态,而StatefulWidget可以通过setState来更新Widget的状态,这里暂时了解这些基本概念,先进入Widget内部看看。

dart 复制代码
@immutable
abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });

  final Key? key;

  @protected
  @factory
  Element createElement();

  @override
  String toStringShort() {
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }

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

  @override
  @nonVirtual
  bool operator ==(Object other) => super == other;

  @override
  @nonVirtual
  int get hashCode => super.hashCode;

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

Widget抽象类的源码很简短,它继承自DiagnosticableTree,此Tree只是用于调试所用,这里就不过多介绍,还是介绍Widget内部的几个方法。

  • createElement()方法用于创建Widget对应的Element对象,Flutter会根据具体的Element生成Render树,Render树就是渲染界面的元素;一个Widget对应一个Element,StatelessWidget创建的是StatelessElement对象,StatefulWidget创建的是StatefulElement对象,关于Widget、Element和Render三者的关系比较复杂,后面会写一篇文章详细介绍它们三个的关系和作用。
  • toStringShort()方法返回的是当前Widget的简短的信息。
  • debugFillProperties()方法用于添加一些调试属性,一般情况下很少用到。
  • canUpdate()方法作用还是比较重要的,它用于判断重建时Widget是否需要更新,默认是对比两个Widget的runtimeType和key是否相同。

Widget源码不复杂,具体复杂的是Widget和Element、Render的关系,这里先暂时了解这么多,更深入的知识我们后续慢慢理解。

StatelessWidget

基础源码

scala 复制代码
abstract class StatelessWidget extends Widget {
  const StatelessWidget({ super.key });

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

  @protected
  Widget build(BuildContext context);
}

StatelessWidget源码中实现了父类的createElement()方法,返回了一个StatelessElement对象,并且将自身传入StatelessElement构造方法当中,然后定义了一个build()方法,用于创建具体的子Widget元素。

接下来我们再看看如何使用StatelessWidget来展示界面UI:

实际使用

scala 复制代码
class MyStatelessWidget extends StatelessWidget {
  const MyStatelessWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return buildScaffold(
      context,
      const Center(
        child: Text('MyStatelessWidget'),
      ),
    );
  }
}

使用StatelessWidget时,我们只需要复写build()方法即可,然后在此方法内部将界面Widget返回,这样就可以实现一个无状态的Widget。

如果我们当前的页面需要信息的更新,那么StatelessWidget就不再适用,此时我们需要选择StatefulWidget来完成界面元素的构造,下面我们接着看看有状态的Widget是如何使用。

StatefulWidget

基础源码

scala 复制代码
abstract class StatefulWidget extends Widget {
  const StatefulWidget({ super.key });

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

  @protected
  @factory
  State createState();
}

StatefulWidget源码中也是实现了父类的createElement()方法,返回了一个StatefulElement对象,并且将自身传入StatefulElement构造方法当中,和StatelessWidget不同的是,它并没有定义build()方法用于创建界面Widget,而是定义了一个createState()方法,用于创建一个State对象,此对象就是用于保存当前Widget中相关的状态信息,更新状态信息也是通过State来完成。

了解了StatefulWidget基础源码之后,我们紧接着来看看如何使用StatefulWidget和State展示一个可修改状态信息的Widget。

实际使用

scala 复制代码
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key});

  @override
  State<StatefulWidget> createState() {
    return _MyState();
  }
}

class _MyState extends State<MyStatefulWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter组件'),
      ),
      body: Center(
        child: Text('count: $count'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            ++count;
          });
        },
        child: const Icon(Icons.favorite),
      ),
    );
  }
}

通过上述的代码可以看到StatefulWidget中逻辑变得很少了,只是复写了createState()方法,返回了_MyState对象而已,具体的子Widget如何创建,Widget的状态信息都下放到_MyState当中了,_MyState继承自State类,然后复写了build()方法,创建界面Widget,并且在内部还定义了一个count变量,用于中间Text显示的文本数据,然后在FloatingActionButton的点击事件中通过setState()方法更新count值,随之Text文本也会跟随着count变化而及时更新。

StatefulWidget可以依赖State对象对内部的状态信息做出更新操作,这也是它和StatelessWidget最大的不同,如果你开发的界面需要界面更新,那么选择StatefulWidget来编写界面是当之无愧的。

下面我们再来看看如何通过State感知当前Widget的生命周期变化。

生命周期

scss 复制代码
class _MyState extends State<MyStatefulWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    debugPrint('build');
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter组件'),
      ),
      body: Center(
        child: Text('count: $count'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            ++count;
          });
        },
        child: const Icon(Icons.favorite),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    debugPrint('initState');
  }

  @override
  void didUpdateWidget(covariant MyStatefulWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    debugPrint('didUpdateWidget');
  }

  @override
  void deactivate() {
    super.deactivate();
    debugPrint('deactivate');
  }

  @override
  void activate() {
    super.activate();
    debugPrint('activate');
  }

  @override
  void reassemble() {
    super.reassemble();
    debugPrint('reassemble');
  }

  @override
  void dispose() {
    super.dispose();
    debugPrint('dispose');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint('didChangeDependencies');
  }
}

上述代码只是简单的在各个生命周期中加了debug信息的打印,下面我们逐一来看下每个生命周期是如何调用的。

当界面第一次被打开时:

initState
didChangeDependencies
build

我们会发现当界面第一次被打开时,生命周期的变化是initStaste -> didChangeDependencies -> build

当界面退出时:

deactivate
dispose

界面从打开状态变为退出状态时,生命周期的变化是deactivate -> dispose

当我们点击Android Studio的hot reload时:

reassemble
build

当我们点击FloatingActionButton将count值改变时:

build

此时之后调用build()方法进行界面的重绘。

分析到这为止,我们可以大致将State的生命周期给梳理成一个框架:

上面这个流程图就将State的从进入界面到退出界面,中间包括hot reload动作和initState动作的生命周期就完整的表述出来了,如果大家有疑问不理解的地方,欢迎在评论区沟通交流😊

不知道看到这里之后,大家有没有发现,activate和didUpdateWidget这两个方法好像自始至终都没有被调用过,难道它们二者不会在生命周期的过程中呈现出来么?不会的,只是它们两个比较特殊,有对应的触发条件,下面我们一个一个来解释下。

didUpdateWidget表示每当Widget配置改变时都会被调用,那么大家是否有种疑问,上面调用setState方法时count不是已经改变了么,为啥没有被调用呢,didUpdateWidget调用时机是父Widget的配置改变需要重建时,子Widget会调用此didUpdateWidget方法进行重建,接下来我们通过代码验证下。

我们将_MyState中原先Center中Text去掉,换成MyFulWidgetChild()组件,此组件中复写didUpdateWidget方法。

scala 复制代码
class _ChildState extends State<MyFulWidgetChild> {
  @override
  Widget build(BuildContext context) {
    return const Text('Child');
  }

  @override
  void didUpdateWidget(covariant MyFulWidgetChild oldWidget) {
    super.didUpdateWidget(oldWidget);
    debugPrint('child didUpdateWidget');
  }
}

此时我们点击_State中FloatingActionButton改变count值时,_ChildState的didUpdateWidget方法就会被调用了,看到这大家应该就会明白了此生命周期的触发的具体条件了。

还剩最后一个activate生命周期,此生命周期根据注释可以得出它的触发条件是:当Widget通过deactivate被移出界面时同时又被加入到另外一个界面它才会被调用,此生命周期日常开发中基本不会使用。

写在最后

本篇文章简单概括性的介绍了Widget和StatelessWidget、StatefulWidget的相关知识,希望可以帮助大家了进一步了解和熟悉StatelessWidget和StatefulWidget的相关知识,后续会循序渐进逐步接触Flutter更多的知识。

我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~

相关推荐
学不会•1 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年6 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder6 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727576 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架