【Flutter 基础】State x Widget

699 阅读6分钟

注:本文从个人公众号(岛前屿端)中迁移重新发布

Flutter 是谷歌的移动 UI 框架,可以从单个代码库快速的为移动端(iOS & Android)、Web、桌面端、嵌入式设备上构建高质量的原生用户界面和应用程序。


上一篇中我们主要说一下简单的 Hello World 是如何实现的。那么这次我就来说说,关于组件的两个形式。

但是在此之前,我觉得还是有必要简单的来说一说关于 Dart 语言的一些东西。

关于 Dart

Dart 语言是由谷歌公司开发的网络编程语言,于2011年10月10日发布。Dart是一个小型的编译型语言

Dart 主要支持和面向 3 个不同平台的开发需求:

  • Dart webdev 开发浏览器应用。
  • Flutter 开发移动应用。
  • Dart VM 开发脚本或者服务器应用。

Dart 的特性

Dart 吸收了一些后端语言和前端语言的一部分特点。在 Dart 中所有的东西都是对象,function 是对象;String 是对象;Number是对象;null 也是对象。(是不是有内味儿了?)

  • 这是一个强类型的语言,可以做类型推断。
  • 一切数据类型都派生自 Object
  • 若无初始化的变量均为 null 默认值
  • 拥有动态类型、泛型以及运算符重载。
  • 拥有顶级函数 main。
  • 没有声明类关键字 "public" "private",以 "_" 开头则表示仅对当前域有效,既是私有的。
  • final 单次声明,既只可赋值一次,之后再次赋值无效。

Ok,关于 Dart 语言就简单介绍这点好了。更多的 Dart 语言内容请参考:dart.dev/

State x Widget 继承关系

在简单介绍关于 Dart 语言后,就可以说说 StatelessWidgetStatefulWidget 了。上一篇中说到 StatelessWidget 和 StatefulWidget 均继承自 Widget,都是 Widget 的抽象类。【传送门】

image.png
(StatelessWidget 的继承关系)

image.png
(StatefulWidget 的继承关系)

从源码中我们可以看出且证实了 StatelessWidgetStatefulWidget继承自 Widget 且都是 Widget 的抽象类 abstract。

这里我将他们的关系整理了一下。

image.png
(StatelessWidget 和 StatefulWidget 的继承关系图)

如图所示,而这也从侧面证实了 Dart 中所有对象都是 Object

在之前的文章中我也有提到过 StatelessWidgetStatefulWidget 虽然都是 Widget 的抽象类。但是他们之间还是有区别的,它们分别用于实现不同场景下使用的 Widget 组件

State x Widget 有状态与无状态

StatelessWidget - 无状态

StatelessWidget 是无状态的,意味着无法通过数据变更而更新。

这样,我们来实现一个小🌰,你就知道了。

项目依然是我们的 MyApp,我们要先 实现一个 名叫 UnchangeWidget 的类,让它 继承于 StatelessWidget

再来实现一个按钮,通过按钮的点击事件来更新我们的文字内容

// 继承 StatelessWidget 意味着无法通过数据变更而改变内容
class UnchangeWidget extends StatelessWidget {
  String setText = '我是原来的文字'; // 声明变量文字 - 默认值

  @override
  Widget build(BuildContext context) {
    return Container(
        child: Column(
      children: <Widget>[
        Text(setText), // 使用变量 setText 来设置 Text 的内容
        RaisedButton(
          child: Text('改变内容'),
          onPressed: () { // 按钮点击事件 - 改变内容
            print('触发了按钮');
            setText = '我是新的文字'; // 赋值新文字内容
          },
        )
      ],
    ));
  }
}

结果如下:

image.png
(文字内容并没有更新)

即使触发了按钮,且对 setText 进行了赋值,但是我们的 Text 内容依然没有更新。

StatefulWidget - 有状态

StatefulWidget 是有状态的,意味着可以通过数据变更而更新,需要通过 setState 来管理状态。

Ok,那么我们再来改写一下这个 UnchangeWidget ,将它改写为 继承于 StatefulWidget

这里可以通过编辑器的快速修复功能来进行代码的改写。

// 继承 StatefulWidget 意味着可以通过数据变更而改变内容
class UnchangeWidget extends StatefulWidget {
  String setText = '原来的内容'; // 变量设置文字 - 默认值

  @override // 创建状态控制类的简写方式,这里的 new 也可以省略
  _UnchangeWidgetState createState() => _UnchangeWidgetState();
}

class _UnchangeWidgetState extends State<UnchangeWidget> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(children: <Widget>[
        Text(widget.setText), // 使用变量 setText 来设置 Text 的内容
        RaisedButton(
          child: Text('改变内容'),
          onPressed: () { // 按钮点击事件 - 改变内容
            print('触发了按钮');
            setState(() { // 调用状态变更函数
              widget.setText = '我是新的文字'; // 赋值新文字内容
              print('状态变更文字更新');
            });
          },
        ),
      ]),
    );
  }
}

结果如下:

image.png
(使用 setState 更新内容)

这里我们可以从结果中看到,通过按钮的触发调用 setState 来进行状态的更新从而更新了 Text 的文字内容。

疑问

这时候一些少侠们就不乐意了:“StatefulWidget 用了 setState 来更新内容,StatelessWidget 没有,不公平!

640.gif

哈哈哈哈,没关系,那么我们就来试一试!我就喜欢你这种敢于发出不同声音的人。

我们现在继续使用 StatefulWidget 的代码,然后直接将继承对象更新为 StatelessWidget

image.png

(更改继承类型为 StatelessWidget)

那么我们来看看结果会是怎么样?在命令行中键入 R 后耐心等待……

image.png

来来来,让我们掌声送给这几位勇敢的少侠,喜提满屏红。

为什么会这样?

为什么会这样呢?因为它们在源码中的实现是不同的。

image.png
(StatelessWidget 源码部分)

image.png
image.png
(StatefulWidget 源码部分)

在源码中 StatelessWidgetStatefulWidget 的实现方式不一样。

我可以简单翻译一下。

StatelessWidget 部分:

此方法的实现必须仅依赖于:

  • widget 的字段,它们本身不能随着时间进行改变,以及任何从 [context] 环境中使用的状态。 如果 widget 的 [build] 方法依赖于其他的东西,那么就请改为使用 [StatefulWidget]。

StatefulWidget 部分:

子类应该重写此方法以返回其关联的新创建的[State]子类实例。
(1)在构建 widget 时,可以同步获取状态信息。
(2)在 widget 的生命周期内,widget 的实现需要确保在状态发生改变时使用 [State.setState] 来及时通知 [State] 需要更改的信息。

所以由此可知 StatelessWidget 是不参与组件生命 State 状态管理的,亦没有对 State 的关联操作,而 StatefulWidget 则是参与实现了 State 的重写,亦要求使用者重写 createState 以保证对 State 的关联

就是说如果你想实现一个动态的可更新数据的组件就使用 StatefulWidget,如果只是想展示静态的文字信息或内容就使用 StatelessWidget


总结

  • 在还不太理解的情况下,可以多去尝试以验证你的想法。
  • 有时候可以考虑看看源码,一些优秀的编程语言或者开源框架在源码部分都会有良好的注释内容,它会告诉你它们在这里做了些什么。