对于 Flutter 初学者来说,StatelessWidgetStatefulWidget是入门时最先接触的两个核心组件,也是构建所有 UI 的基础。很多新手会困惑:“这两个组件到底有啥区别?什么时候该用哪个?” 其实核心答案就一句话 ——是否需要维护 “可变状态”

先搞懂:什么是 Flutter 里的 “状态”?

在聊组件区别前,先明确一个关键概念 ——状态(State):状态就是组件中 “会变化的数据”,这些数据的改变会导致 UI 重新渲染。

比如:计数器的数字、表单输入的文字、网络请求后返回的列表数据、开关的选中状态等,都是典型的 “状态”。

而组件的核心差异,本质就是 “是否能持有并管理这些可变数据”:

  • StatelessWidget:不能持有可变状态,UI 一旦创建就固定不变;
  • StatefulWidget:可以持有可变状态,状态变化时会触发 UI 重新渲染。

StatelessWidget:无状态组件的本质

1. 核心定义

StatelessWidget 是 “无状态组件”,它的核心特点是 不可变(immutable)—— 所有属性(成员变量)都必须是final类型,一旦初始化就不能修改。

因为属性不可变,所以它的 UI 是 “静态” 的:创建后不会因为内部数据变化而更新,只能通过父组件传递新的属性来重新构建。

2. 关键特点

  • 属性全是final,不可修改;
  • 生命周期极简单:只有一个核心方法build(BuildContext context),负责构建 UI;
  • 轻量高效:无额外状态管理开销,构建速度快;
  • 数据来源:只能通过构造函数从父组件接收(或使用全局数据)。

3. 简单示例:静态个人信息卡片

// 无状态组件:展示固定的个人信息,数据不会变化
class ProfileCard extends StatelessWidget {
  // 属性必须是final(不可变)
  final String name;
  final int age;
  final String avatarUrl;

  // 构造函数接收父组件传递的数据
  const ProfileCard({
    super.key,
    required this.name,
    required this.age,
    required this.avatarUrl,
  });

  // 唯一核心方法:构建UI
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12),
        color: Colors.white,
        boxShadow: [BoxShadow(color: Colors.grey[200], blurRadius: 4)],
      ),
      child: Column(
        children: [
          Image.network(avatarUrl, width: 80, height: 80, fit: BoxFit.cover),
          const SizedBox(height: 8),
          Text("姓名:$name", style: const TextStyle(fontSize: 18)),
          Text("年龄:$age", style: const TextStyle(color: Colors.grey)),
        ],
      ),
    );
  }
}

这个组件的 UI 完全由传入的nameageavatarUrl决定,只要这些参数不变,UI 就永远不会变 —— 这就是无状态组件的核心特征。

StatefulWidget:有状态组件的核心

1. 核心定义

StatefulWidget 是 “有状态组件”,它的核心特点是 可变(mutable)—— 可以持有会变化的数据,并且数据变化时会触发 UI 重新渲染。

它的设计很特殊:组件本身是final的(不可变),但会关联一个独立的State对象,可变状态存储在State,而不是组件本身。这样设计的目的是:组件重建时,State对象不会被销毁,状态能得以保留。

2. 关键特点

  • 组件本身是final,但State对象可存储可变数据;
  • 生命周期复杂:包含初始化、构建、更新、销毁等多个阶段;
  • 可响应状态变化:通过setState(() {})方法通知 Flutter“状态变了,需要重新构建 UI”;
  • 数据来源:可自己持有(如计数器)、从父组件接收、从网络 / 数据库获取。

3. 核心生命周期

不用死记所有生命周期方法,重点掌握 3 个核心阶段:

  1. 初始化阶段initState() → 组件创建时执行一次,用于初始化状态(如初始化计数器、请求初始数据);
  2. 构建阶段build(BuildContext context) → 状态变化时触发,构建 UI(和 StatelessWidget 的 build 作用一致);
  3. 销毁阶段dispose() → 组件从屏幕消失时执行一次,用于释放资源(如取消网络请求、关闭流)。

4. 简单示例:计数器组件

// 有状态组件:计数器,数字会随点击变化
class CounterWidget extends StatefulWidget {
  // 组件本身是final,若有属性也需是final
  final String title;

  const CounterWidget({super.key, required this.title});

  // 关键:创建State对象,状态存储在State中
  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

// State类:持有可变状态
class _CounterWidgetState extends State<CounterWidget> {
  // 可变状态:计数器数字
  int _count = 0;

  // 初始化阶段:组件创建时执行一次
  @override
  void initState() {
    super.initState();
    print("组件初始化:计数器初始值为$_count");
  }

  // 点击事件:修改状态并触发UI重建
  void _incrementCounter() {
    // setState:通知Flutter状态已变,需要重新执行build
    setState(() {
      _count++;
    });
  }

  // 构建UI:状态变化时重新执行
  @override
  Widget build(BuildContext context) {
    print("UI重新构建:当前计数为$_count");
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)), // 通过widget访问父组件属性
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("你点击了按钮的次数:"),
            Text(
              "$_count",
              style: Theme.of(context).textTheme.displayMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: const Icon(Icons.add),
      ),
    );
  }

  // 销毁阶段:组件消失时释放资源
  @override
  void dispose() {
    super.dispose();
    print("组件销毁:释放资源");
  }
}

这个组件中,_count是可变状态,点击按钮时通过setState修改它,Flutter 会自动重新执行build方法,更新显示的数字 —— 这就是有状态组件的核心工作流程。

核心区别对比

对比维度 StatelessWidget(无状态) StatefulWidget(有状态)
核心特征 不可变(属性全final),无内部可变状态 组件不可变,但State对象持有可变状态
UI更新方式 只能通过父组件传递新属性重建 调用setState或状态依赖变化(Provider/Riverpod)
生命周期 简单,仅build方法 复杂,含initState、build、dispose等阶段
性能开销 轻量,无状态管理开销 略高,需维护State对象和生命周期

选型指南:什么时候该选哪个?

核心原则:能不用 Stateful,就不用 Stateful(性能更优);必须用 Stateful,才用 Stateful(满足动态需求)。

优先选 StatelessWidget 的场景

  1. 展示静态数据:UI 一旦创建就不会变化,数据来自父组件或全局配置;示例:关于我们页面、帮助中心、静态列表(数据固定)、图标按钮(无状态变化)。
  2. 作为复杂组件的子组件:如果子组件不需要自己管理状态,仅接收父组件传递的参数展示;示例:列表项组件(数据由列表父组件传递,无自身交互)、标签组件、图标组件。
  3. 纯展示型 UI:无用户交互(点击、输入、滑动等),仅用于展示内容;示例:文本展示、图片展示、分割线、卡片组件(静态数据)。

必须选 StatefulWidget 的场景

  1. 需要用户交互导致 UI 变化:用户操作后数据改变,UI 需同步更新;示例:计数器、开关按钮、复选框、表单输入(文本框、下拉选择)。
  2. 需要异步数据刷新:组件加载后需要请求网络、读取本地数据库,数据返回后更新 UI;示例:商品列表(网络请求后展示)、用户信息(从本地缓存读取)、实时聊天消息。
  3. 需要生命周期管理:组件创建时初始化资源、销毁时释放资源;示例:倒计时组件(initState 启动定时器,dispose 取消定时器)、视频播放组件(dispose 关闭播放器)。
  4. 状态依赖变化:依赖的状态(如 Provider/Riverpod 管理的全局状态)变化时,需要重新渲染;示例:登录状态变化后显示不同 UI、主题切换后同步更新组件样式。

常见踩坑点

1. 把可变数据放在 StatelessWidget 中

错误:在 StatelessWidget 中定义非 final 的变量,想通过修改变量更新 UI;
后果:变量修改后 UI 不会刷新(因为 StatelessWidget 不会重新 build);
解决:改用 StatefulWidget,把可变数据放在 State 中。

2. 不必要的 StatefulWidget

错误:一个仅展示父组件传递数据的组件,做成了 Stateful;
后果:增加不必要的性能开销;
解决:改成 StatelessWidget,用 final 属性接收数据。

3. 在 build 方法中做耗时操作

错误:在 Stateless/Stateful 的 build 方法中请求网络、读取文件;
后果:build 方法可能频繁执行(如父组件重建),导致重复耗时操作,UI 卡顿;
解决:在 Stateful 的 initState 中执行一次耗时操作,或用 FutureBuilder/StreamBuilder 处理异步。

4. 忘记在 dispose 中释放资源

错误:在 Stateful 中创建定时器、网络请求、流,却不在 dispose 中取消;
后果:内存泄露(组件销毁后资源仍在运行);
解决:在 dispose 方法中取消定时器、关闭流、取消网络请求。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐