Flutter 系列教程:核心概念 - StatelessWidget vs. StatefulWidget

在 Flutter 的世界里,记住一句箴言:"万物皆 Widget"。您在屏幕上看到的一切------按钮、文本、图片、甚至整个页面布局------都是由一个个 Widget 嵌套组合而成的。

然而,这些 Widget 并非生而平等。它们主要分为两大阵营:StatelessWidget (无状态组件) 和 StatefulWidget (有状态组件)。理解它们的区别,是构建任何复杂 Flutter 应用的基石。

学习目标

  • 理解什么是 "状态" (State)。
  • 掌握 StatelessWidget 的特点和使用场景。
  • 掌握 StatefulWidget 的结构、生命周期和 setState 的核心作用。
  • 学会根据需求选择合适的 Widget 类型。

1. 什么是 "状态" (State)?

在 Flutter 中,"状态" 指的是可以在 Widget 生命周期内发生改变的数据。简单来说,任何可能变化并影响 UI 展现的信息,都是状态。

  • 例如:复选框是否被选中、计数器当前的数字、滑动条的位置、用户在输入框中输入的文字等。

根据一个 Widget 能否在其内部管理这些可变的状态,我们将其划分为 StatelessWidgetStatefulWidget


2. StatelessWidget (无状态组件)

顾名思义,StatelessWidget 自身是没有 "状态" 的。

  • 核心特点 :一旦被创建,其内部的数据和外观就不可改变 (immutable)。它只负责根据创建时传入的配置信息进行 "静态" 渲染。
  • 生命周期 :非常简单,只有构造函数和 build 方法。它被渲染后,就不会再主动重绘自己。
  • 工作方式:像一个"画匠",你告诉它要画什么(通过构造函数传递参数),它就一次性画好,然后就不再管了。如果想让它画出不同的内容,唯一的办法就是销毁它,然后用新的参数创建一个新的实例。

何时使用 StatelessWidget?

当你构建的 UI 部分不依赖于任何内部可变的数据,只依赖于外部传入的配置时,就应该使用 StatelessWidget

  • 常见场景IconTextRaisedButton (按钮本身)、静态的图片、应用的 Logo、一个设计好的卡片模板等。

代码示例:创建一个静态的用户信息卡片

让我们创建一个简单的 UserInfoCard,它接收一个名字和等级,然后显示出来。这个卡片一旦显示,内容就是固定的。

less 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('StatelessWidget Demo')),
        body: const Center(
          // 使用我们自定义的 StatelessWidget
          child: UserInfoCard(
            name: 'Flutter Coder',
            level: 'Expert',
            avatarColor: Colors.blue,
          ),
        ),
      ),
    );
  }
}

// 这是我们的自定义 StatelessWidget
class UserInfoCard extends StatelessWidget {
  // 属性一旦设置,就不能再改变 (final)
  final String name;
  final String level;
  final Color avatarColor;

  // 构造函数,接收外部传入的数据
  const UserInfoCard({
    super.key,
    required this.name,
    required this.level,
    this.avatarColor = Colors.grey,
  });

  @override
  Widget build(BuildContext context) {
    // build 方法描述了 UI 的样子
    // 它只依赖于构造函数传入的 final 变量
    return Container(
      padding: const EdgeInsets.all(16.0),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(10.0),
        boxShadow: [
          BoxShadow(
            color: Colors.grey.withOpacity(0.3),
            spreadRadius: 2,
            blurRadius: 5,
          ),
        ],
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min, // 让 Row 的宽度自适应内容
        children: [
          CircleAvatar(
            backgroundColor: avatarColor,
            radius: 30,
            child: const Icon(Icons.person, color: Colors.white, size: 30),
          ),
          const SizedBox(width: 16), // 用于添加间距
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                name,
                style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
              Text(
                level,
                style: const TextStyle(fontSize: 16, color: Colors.grey),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

在这个例子中,UserInfoCardnamelevel 是通过构造函数传入的,并且被 final 关键字修饰,意味着它们在 Widget 内部是不可变的。build 方法只负责把这些数据显示出来。


3. StatefulWidget (有状态组件)

StatelessWidget 相对,StatefulWidget 自身可以持有和管理可变的状态 (State)

  • 核心特点 :当其内部状态改变时,它可以主动触发自身重绘 (rebuild),从而更新 UI 显示。
  • 结构StatefulWidget 比较特殊,它由两个类 组成:
    1. 一个继承自 StatefulWidget 的类:这个类本身是不可变的,只负责存储一些最终配置 (类似 StatelessWidget)。它最重要的方法是 createState()
    2. 一个继承自 State 的类:这个类才是核心,它负责持有可变的状态数据 ,并且包含了 build 方法来构建 UI。这个 State 对象可以在多次 build 之间保持存活。

为什么需要两个类? 这是 Flutter 的一种性能优化设计。当 Flutter 重建 UI 时,StatefulWidget 的实例可能会被销毁和重建,但它的 State 对象会被保留下来,这样就可以在多次重建之间保持状态数据不丢失。

State 对象的生命周期和核心方法

  • initState(): State 对象被创建后调用的第一个方法,且只调用一次。通常在这里执行一些初始化操作,如初始化变量、订阅事件等。
  • build(): 在 initState() 之后立即调用,并且每当 UI 需要更新时都会被调用。
  • setState(VoidCallback fn): 最核心的方法! 当你想改变状态并让 UI 更新时,你必须setState 的回调函数中修改你的状态变量。调用 setState() 会通知 Flutter:"嘿,我这儿有数据变了,请重新调用我的 build 方法来更新屏幕!"
  • dispose(): 当 Widget 被从 Widget 树中永久移除时调用。通常在这里执行清理工作,如取消定时器、移除监听器等,以防内存泄漏。

代码示例:一个经典的计数器

这个例子完美地展示了 StatefulWidget 如何管理自己的状态。

scala 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('StatefulWidget Demo')),
        body: const Center(
          // 使用我们自定义的 StatefulWidget
          child: Counter(),
        ),
      ),
    );
  }
}

// 1. 继承自 StatefulWidget 的类 (不可变的部分)
class Counter extends StatefulWidget {
  const Counter({super.key});

  // 核心方法:创建与 Widget 关联的 State 对象
  @override
  State<Counter> createState() => _CounterState();
}

// 2. 继承自 State 的类 (持有可变状态并构建 UI 的部分)
// 类名前的下划线 `_` 表示这是私有的,只能在当前文件中访问
class _CounterState extends State<Counter> {
  // 这就是我们的 "状态":一个可以改变的计数器变量
  int _counter = 0;

  // 一个改变状态的方法
  void _incrementCounter() {
    // 必须在 setState 中修改状态,这样 Flutter 才知道需要更新 UI
    setState(() {
      // 在这个回调函数里,我们改变 _counter 的值
      _counter++;
    });
  }

  // State 对象的 build 方法,用于构建 UI
  @override
  Widget build(BuildContext context) {
    // 每当 setState 被调用,这个 build 方法就会被重新执行
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        const Text(
          'You have pushed the button this many times:',
          style: TextStyle(fontSize: 18),
        ),
        Text(
          '$_counter', // UI 直接显示状态变量 _counter 的值
          style: const TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 20),
        ElevatedButton(
          onPressed: _incrementCounter, // 按下按钮时,调用改变状态的方法
          child: const Icon(Icons.add),
        ),
      ],
    );
  }
}

在这个例子中,每次点击按钮,_incrementCounter 方法被调用。方法内部的 setState() 告诉 Flutter 框架 _counter 变量已经改变了。于是,Flutter 会重新调用 _CounterStatebuild 方法,新的 _counter 值被渲染到屏幕上,你就看到了数字的增加。


4. 如何选择:Stateless 还是 Stateful?

问自己一个简单的问题:"这个 Widget 在被渲染出来后,它的显示内容是否需要根据用户交互或内部数据变化而改变?"

  • 如果答案是"否" ,它只是一个静态的展示,那就用 StatelessWidget
  • 如果答案是"是" ,比如它包含一个需要用户输入的表单,或者一个可以点击切换状态的按钮,那就用 StatefulWidget

经验法则 :优先使用 StatelessWidget。只有当你确定需要管理内部状态时,才将其重构为 StatefulWidget。这会让你的应用结构更清晰,性能也更好。

总结

特性 StatelessWidget StatefulWidget
状态 不可变 (Immutable) 可变 (Mutable)
核心 只是一个 build 方法 State 对象 + setState()
结构 单一类 两个类 (Widget + State)
生命周期 简单(构造函数 + build 复杂 (initState, build, dispose等)
用途 静态展示UI,如 Icon, Text 动态UI,响应交互和数据变化

至此,您已经掌握了 Flutter 中最核心的 Widget 概念。理解了 StatelessWidgetStatefulWidget 的区别,就等于拿到了开启 Flutter 大门的钥匙。

接下来,我们将学习如何使用 Flutter 提供的各种布局 Widget (如 Container, Row, Column 等) 来像搭积木一样组合出漂亮的界面。我们下一篇教程见!

相关推荐
叫我詹躲躲2 小时前
别再手写正则了!20 + 证件 / 手机号 / 邮箱验证函数,直接复制能用
前端·javascript·正则表达式
猪哥帅过吴彦祖2 小时前
第 4 篇:赋予表面生命 - WebGL 纹理贴图
前端·javascript·webgl
郝学胜-神的一滴2 小时前
解析前端框架 Axios 的设计理念与源码
开发语言·前端·javascript·设计模式·前端框架·软件工程
aixfe2 小时前
Ant Design V5 Token 体系颜色处理最佳实践
前端
yanessa_yu3 小时前
前端请求竞态问题
前端
web打印社区3 小时前
如何在 Vue 中打印页面:直接用 web-print-pdf(npm 包)
前端·vue.js·pdf
web打印社区3 小时前
最简单的 Web 打印方案:用 5 分钟上手 web-print-pdf(npm 包)
前端·pdf·npm
转转技术团队3 小时前
AI在前后端联调提效的实践
前端·后端