Flutter StatelessWidget vs StatefulWidget 深入理解
理解 StatelessWidget 和 StatefulWidget 的区别是 Flutter 开发的基础。
本节将深入分析这两种 Widget 的使用场景和内部工作机制。
核心区别
Flutter 中有两种主要类型的 Widget,它们的根本区别在于是否能持有可变状态。
| 特性 | StatelessWidget | StatefulWidget |
|---|---|---|
| 状态 | 不可变(immutable) | 可变(mutable) |
| 重建时机 | 仅在父 Widget 重建时 | 状态变化时 + 父 Widget 重建 |
| 性能 | 更高(无需监听状态) | 略低(需要状态管理) |
| 使用场景 | 静态内容 | 动态交互内容 |
StatelessWidget 详解
StatelessWidget 适合用于不随时间变化的内容。一旦创建,其所有属性都是 final 的,不可修改。
使用场景
- 显示静态文本、图片、图标
- 展示数据列表(数据源来自父 Widget)
- 不包含用户交互的布局容器
- 表单中的只读展示部分
实例:StatelessWidget 完整示例
import 'package:flutter/material.dart';
// 用户信息展示组件(无状态)
class UserCard extends StatelessWidget {
// 属性定义为 final,确保不可变
final String name;
final String email;
final String avatarUrl;
// const 构造函数提高性能
const UserCard({
super.key,
required this.name,
required this.email,
this.avatarUrl = '',
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// 头像
CircleAvatar(
backgroundImage: avatarUrl.isNotEmpty
? NetworkImage(avatarUrl)
: null,
child: avatarUrl.isEmpty
? const Icon(Icons.person)
: null,
),
const SizedBox(width: 16),
// 用户信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
email,
style: TextStyle(
color: Colors.grey[600],
),
),
],
),
),
],
),
),
);
}
}
// 用户信息展示组件(无状态)
class UserCard extends StatelessWidget {
// 属性定义为 final,确保不可变
final String name;
final String email;
final String avatarUrl;
// const 构造函数提高性能
const UserCard({
super.key,
required this.name,
required this.email,
this.avatarUrl = '',
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// 头像
CircleAvatar(
backgroundImage: avatarUrl.isNotEmpty
? NetworkImage(avatarUrl)
: null,
child: avatarUrl.isEmpty
? const Icon(Icons.person)
: null,
),
const SizedBox(width: 16),
// 用户信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
email,
style: TextStyle(
color: Colors.grey[600],
),
),
],
),
),
],
),
),
);
}
}
StatefulWidget 详解
StatefulWidget 适合用于需要响应用户交互或数据变化的场景。它由两部分组成:Widget 本身(不可变)和 State 对象(可变)。
使用场景
- 计数器、计时器等动态数据
- 表单输入
- 动画控制
- 网络请求状态展示
- 用户交互响应
State 对象生命周期
| 方法 | 说明 |
|---|---|
| initState | Widget 首次创建时调用,用于初始化状态 |
| didChangeDependencies | Widget 的依赖变化时调用 |
| build | 构建 UI,每次状态变化都会调用 |
| didUpdateWidget | 父 Widget 重建时调用 |
| dispose | Widget 被移除时调用,用于清理资源 |
实例:StatefulWidget 完整示例
import 'package:flutter/material.dart';
// 计数器应用
class CounterApp extends StatefulWidget {
const CounterApp({super.key});
@override
State<CounterApp> createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
// 计数器状态
int _counter = 0;
// 计时器(用于演示 dispose)
Timer? _timer;
// 1. initState - 初始化状态
@override
void initState() {
super.initState();
// 初始化计数器
_counter = 0;
// 启动一个定时器(每秒增加一次)
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_increment();
});
}
// 2. didChangeDependencies - 依赖变化时调用
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
// 3. build - 构建 UI
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('计数器'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'当前计数:',
style: TextStyle(fontSize: 20),
),
Text(
'$_counter',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
],
),
),
// 增加按钮
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
// 增加计数器的方法
void _increment() {
// 必须调用 setState 来触发 UI 更新
setState(() {
_counter++;
});
}
// 4. didUpdateWidget - 父 Widget 重建时调用
@override
void didUpdateWidget(CounterApp oldWidget) {
super.didUpdateWidget(oldWidget);
}
// 5. dispose - 清理资源
@override
void dispose() {
// 取消定时器,防止内存泄漏
_timer?.cancel();
super.dispose();
}
}
// 计数器应用
class CounterApp extends StatefulWidget {
const CounterApp({super.key});
@override
State<CounterApp> createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
// 计数器状态
int _counter = 0;
// 计时器(用于演示 dispose)
Timer? _timer;
// 1. initState - 初始化状态
@override
void initState() {
super.initState();
// 初始化计数器
_counter = 0;
// 启动一个定时器(每秒增加一次)
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_increment();
});
}
// 2. didChangeDependencies - 依赖变化时调用
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
// 3. build - 构建 UI
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('计数器'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'当前计数:',
style: TextStyle(fontSize: 20),
),
Text(
'$_counter',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
],
),
),
// 增加按钮
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
// 增加计数器的方法
void _increment() {
// 必须调用 setState 来触发 UI 更新
setState(() {
_counter++;
});
}
// 4. didUpdateWidget - 父 Widget 重建时调用
@override
void didUpdateWidget(CounterApp oldWidget) {
super.didUpdateWidget(oldWidget);
}
// 5. dispose - 清理资源
@override
void dispose() {
// 取消定时器,防止内存泄漏
_timer?.cancel();
super.dispose();
}
}
setState 的正确使用
在 StatefulWidget 中,必须通过 setState 来通知 Flutter 状态已更改。
正确用法
实例:正确的 setState 用法
// 正确:在 setState 中更新状态
void _increment() {
setState(() {
_counter++;
});
}
// 正确:状态更新与 UI 无关时可以不用 setState
void _someInternalLogic() {
// 内部逻辑,不影响 UI
_calculateSomething();
}
// 正确:异步操作后更新状态
Future<void> _loadData() async {
final data = await fetchData();
setState(() {
_items = data;
});
}
void _increment() {
setState(() {
_counter++;
});
}
// 正确:状态更新与 UI 无关时可以不用 setState
void _someInternalLogic() {
// 内部逻辑,不影响 UI
_calculateSomething();
}
// 正确:异步操作后更新状态
Future<void> _loadData() async {
final data = await fetchData();
setState(() {
_items = data;
});
}
错误用法
实例:错误的 setState 用法
// 错误:修改状态但不调用 setState
void _increment() {
_counter++; // 状态已改变,但 UI 不会更新!
}
// 错误:在 build 方法中调用 setState
@override
Widget build(BuildContext context) {
// 永远不要在 build 中调用 setState!
setState(() {
_counter++; // 这会导致无限循环
});
return Text('$_counter');
}
void _increment() {
_counter++; // 状态已改变,但 UI 不会更新!
}
// 错误:在 build 方法中调用 setState
@override
Widget build(BuildContext context) {
// 永远不要在 build 中调用 setState!
setState(() {
_counter++; // 这会导致无限循环
});
return Text('$_counter');
}
setState 会在下一次帧绘制时触发 build 方法的重新调用。
频繁调用 setState 或在 build 方法中调用 setState 都可能导致性能问题。
如何选择
在开发时,应该根据实际需求选择合适的 Widget 类型:
选择 StatelessWidget 当
- Widget 的外观不依赖于任何可变状态
- 所有需要的数据都通过构造函数从父 Widget 传入
- Widget 不需要响应用户交互
- 追求最佳性能
选择 StatefulWidget 当
- Widget 需要维护内部状态
- 需要响应用户输入(点击、滑动等)
- 需要响应数据变化(如网络请求返回)
- 需要控制动画
