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

285 阅读7分钟

在 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,它接收一个名字和等级,然后显示出来。这个卡片一旦显示,内容就是固定的。

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 如何管理自己的状态。

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。这会让你的应用结构更清晰,性能也更好。

总结

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

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

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