变化莫测 —— StatefulWidget 与 StatelessWidget 之争

1 阅读4分钟

StatefulWidgetStatelessWidget 是 Flutter 中用于构建用户界面的两种基本组件。它们之间的主要区别在于是否拥有可变状态,本质上是对是否需要持久保存状态并响应状态变化的建模抽象;

下文将结合运行周期,适用场景,关键结构进行综合对比分析;

一、运行周期

StatefulWidget 构建

sequenceDiagram
    participant FlutterFramework
    participant StatefulWidget
    participant StatefulElement
    participant State

    FlutterFramework->>StatefulWidget: 构造函数()
    FlutterFramework->>StatefulWidget: createElement()
    StatefulWidget->>StatefulElement: 构造 StatefulElement
    StatefulElement->>StatefulWidget: createState()
    StatefulElement->>State: attach(), set _widget
    FlutterFramework->>State: initState()
    FlutterFramework->>State: didChangeDependencies()
    FlutterFramework->>State: build()
    State-->>FlutterFramework: 返回 Widget 树

    Note over State: 调用 setState() 后流程
    FlutterFramework->>State: setState()
    State->>StatefulElement: markNeedsBuild()
    FlutterFramework->>State: build()(重新构建)


  1. 创建 Widget & Element

    • 类似 Stateless,先构造 StatefulWidgetStatefulElement
  2. 状态初始化

    • 调用 createState() 创建并绑定一个 State 对象;
    • State 对象接管生命周期,包含状态管理、构建逻辑;
  3. 生命周期初始化

    • initState() 在挂载前只调用一次,常用于初始化资源;
    • didChangeDependencies() 在依赖 InheritedWidget 时首次或变更时调用;
  4. 构建 UI

    • 调用 State.build(),将结果 Widget 树返回给框架;
  5. 状态更新触发构建

    • 调用 setState() 触发 markNeedsBuild()
    • 框架在下一帧调用 State.build() 重建子 Widget 树(Element 和 RenderObject 可能重用);

StatelessWidget 构建

sequenceDiagram
    participant FlutterFramework
    participant StatelessWidget
    participant StatelessElement

    FlutterFramework->>StatelessWidget: 构造函数()
    FlutterFramework->>StatelessWidget: createElement()
    StatelessWidget->>StatelessElement: 构造 StatelessElement
    FlutterFramework->>StatelessElement: mount()
    StatelessElement->>StatelessWidget: build()
    StatelessElement-->>FlutterFramework: 返回 Widget 树

    Note over StatelessElement: 外部状态更新时调用 build()

  1. 创建实例

    • 框架先调用 StatelessWidget 的构造函数生成 Widget 实例(是配置数据,不保存状态);
  2. Element 创建

    • 调用 createElement() 方法创建一个 StatelessElement,它代表 Widget 在 Element 树中的位置和生命周期管理;
  3. 挂载阶段mount()):

    • StatelessElement 与其 Widget 配对,并将 build() 构建出来的子 Widget 树嵌入父节点;
  4. 构建 UI

    • StatelessWidget.build() 方法生成 Widget 树,传递回框架以便进一步构建 Element 与 RenderObject。
  5. 无状态特点

    • 如果外部触发 UI 更新,会销毁原 Widget 并重新构建,无法保留状态。

二、适用场景

StatefulWidget 适合场景

  1. 可变状态: StatefulWidget 可以包含可变的状态(State),在状态发生变化时重建部分或全部 UI;
  2. 生命周期: StatefulWidget 有一个关联的 State 对象,该对象的生命周期负责管理状态的变化。State 对象在 createState 方法中创建,可以在整个组件的生命周期内保持状态;
  3. 场景: 当部分 UI 需要根据用户交互、网络请求或其他异步操作来动态更新时,使用 StatefulWidget

示例:

class CounterWidget extends StatefulWidget {
  @override
  _CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Counter: $_counter'),
        ElevatedButton(
          onPressed: _incrementCounter,
          child: Text('Increment'),
        ),
      ],
    );
  }
}
  • 表单输入、倒计时、动画、交互反馈等内部状态变化。
  • 需要控制生命周期的场景(如网络请求绑定释放)。

StatelessWidget 适合场景

  1. 不可变状态: StatelessWidget 没有可变的状态,一旦构建完成,其内容就不再改变。
  2. 生命周期: 由于没有状态变化,StatelessWidget 不需要关联的 State 对象,因此没有生命周期方法。它在构建后就不再改变。
  3. 场景: 当界面内容在构建后不再变化,且不受用户交互、异步操作等影响时,使用 StatelessWidget

示例:

class GreetingWidget extends StatelessWidget {
  final String name;

  GreetingWidget(this.name);

  @override
  Widget build(BuildContext context) {
    return Text('Hello, $name!');
  }
}
  • 页面标题栏、静态卡片组件等无状态结构。
  • 响应来自外部(如 Provider、Bloc)的状态更新。

小结

场景推荐使用原因
视图稳定,外部驱动StatelessWidget性能高,代码简洁
内部控制状态变更StatefulWidget可维护生命周期,适用于动画/表单等复杂交互
响应框架状态管理StatelessWidget与 Provider、Riverpod、Bloc 等兼容性强

三、结构对比

StatefulWidget 结构

面向生命周期、事件驱动的状态响应机制;

构建 StatefulWidget:
┌─────────────┐
│ Widget.createState() ─────┐
└──────┬────────────────────┘
       ▼
 StatefulElement 持有 State 对象
       ▼
State.initState()
       ▼
State.build()
       ▼
setState → markNeedsBuild → build()

抽象结构

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key? key }) : super(key: key);

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

  @protected
  State createState();  // 需子类实现
}

setState 观察者

sequenceDiagram
    participant 用户操作
    participant State
    participant StatefulElement
    participant FlutterFramework

    用户操作->>State: setState(() { ... })
    State->>State: 更新内部变量
    State->>StatefulElement: markNeedsBuild()
    FlutterFramework->>State: build()
    State-->>FlutterFramework: 返回新 Widget 树
abstract class State<T extends StatefulWidget> {
  T get widget => _widget;
  BuildContext get context => _element;

  void initState() {}
  void didUpdateWidget(T oldWidget) {}
  void setState(VoidCallback fn) { ... } // 状态更新入口
  void dispose() {}
}

StatelessWidget 结构

更倾向于“函数式编程思维”:构建就是传参 + 返回视图;

构建 StatelessWidget:
┌─────────────┐
│ Widget.build│
└──────┬──────┘
       ▼
 StatelessElement 负责构建,不保存状态,重建时直接替换。

抽象结构

abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key? key }) : super(key: key);

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

  Widget build(BuildContext context);  // 需子类实现
}

小结

  • StatefulWidget 适用于存在内部状态的组件,其状态保存在 State 对象中,并通过 setState() 驱动重建。
  • StatelessWidget 用于构建不可变、纯函数式 UI,不保存状态,build() 是其核心方法。

两者的 createElement() 方法返回不同的 Element 类型,决定其生命周期和状态管理能力。

总结

特性StatelessWidgetStatefulWidget
状态是否可变❌ 不可变✅ 可变(可维护局部状态)
状态保存位置Widget 自身通过 State 对象单独维护
生命周期构建即结束,无后续生命周期管理有完整生命周期(initState、dispose 等)
性能开销小,适合轻量 UI稍大,需要额外维护 State
使用场景UI 不变、响应外部状态UI 受内部状态控制(如表单、动画等)

从设计模式角度看,StatefulWidget 使用模板方法模式封装生命周期,StatelessWidget 强调函数式声明式编程。选择应根据是否需要内部状态变化、性能开销考虑以及状态是否可通过外部状态管理解决来决定。

在实际开发中,常常会同时使用这两者,将界面拆分为有状态和无状态的组件,以达到更好的组织和性能优化。