受控组件vs非受控组件
受控组件 vs 非受控组件
受控组件vs非受控组件
受控组件 vs 非受控组件
概念理解
🎯 受控组件 (Controlled Component)
- 数据由父组件控制:组件的值通过
value属性从外部传入 - 单向数据流:数据从父组件流向子组件
- 状态提升:状态保存在父组件中,子组件只是”展示”数据
🎮 非受控组件 (Uncontrolled Component)
- 数据由组件自己管理:组件内部维护自己的状态
- 双向数据流:组件内部可以修改自己的状态
- 状态内聚:状态保存在组件内部
📊 对比表格
| 特性 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据来源 | 通过 value从外部传入 | 组件内部管理 |
| 状态位置 | 父组件 | 组件自身 |
| 数据流 | 单向 | 双向 |
| 可预测性 | 高 | 低 |
| 调试难度 | 低 | 高 |
| 适用场景 | 表单、复杂交互 | 简单独立组件 |
💡 核心原则
在 Flutter 中,推荐使用受控组件,因为:
- 数据流更清晰
- 更容易调试
- 符合 Flutter 的声明式UI理念
- 便于状态管理
🛠️ 代码示例
1. 受控组件示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/// 受控输入框组件
class ControlledTextField extends StatelessWidget {
final String value;
final ValueChanged<String> onChanged;
const ControlledTextField({
super.key,
required this.value,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return TextField(
controller: TextEditingController(text: value),
onChanged: onChanged,
);
}
}
/// 使用受控组件
class ParentWidget extends StatefulWidget {
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
String _inputValue = ''; // 状态在父组件
@override
Widget build(BuildContext context) {
return Column(
children: [
// 数据流向:父组件 → 子组件
ControlledTextField(
value: _inputValue, // 数据从父组件传入
onChanged: (newValue) {
setState(() {
_inputValue = newValue; // 回调通知父组件更新状态
});
},
),
Text('当前值: $_inputValue'), // 实时显示,数据一致
],
);
}
}
2. 非受控组件示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/// 非受控输入框组件
class UncontrolledTextField extends StatefulWidget {
final String initialValue;
final ValueChanged<String>? onChanged;
const UncontrolledTextField({
super.key,
this.initialValue = '',
this.onChanged,
});
@override
State<UncontrolledTextField> createState() => _UncontrolledTextFieldState();
}
class _UncontrolledTextFieldState extends State<UncontrolledTextField> {
late TextEditingController _controller; // 状态在组件内部
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.initialValue);
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
onChanged: (value) {
widget.onChanged?.call(value); // 可选的回调
},
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
/// 使用非受控组件
class ParentWidget extends StatefulWidget {
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
String _displayValue = '';
@override
Widget build(BuildContext context) {
return Column(
children: [
UncontrolledTextField(
initialValue: '初始值',
onChanged: (value) {
setState(() {
_displayValue = value; // 只能通过回调获取值
});
},
),
Text('显示值: $_displayValue'), // 可能不同步!
ElevatedButton(
onPressed: () {
// 问题:无法直接获取输入框的当前值
// _displayValue 可能不是最新的
},
child: Text('提交'),
),
],
);
}
}
🔄 数据流对比
受控组件数据流
1
2
3
父组件状态 → value属性 → 子组件显示 → 用户操作 → onChanged回调 → 更新父组件状态
↓
数据始终同步
非受控组件数据流
初始值 → 子组件内部状态 → 用户操作 → 内部状态变化 → 可选回调通知父组件
↓
数据可能不同步
🎯 实际场景示例
场景:表单提交
受控组件方式(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class UserForm extends StatefulWidget {
@override
State<UserForm> createState() => _UserFormState();
}
class _UserFormState extends State<UserForm> {
// 所有状态在父组件统一管理
String _name = '';
String _email = '';
bool _subscribe = false;
void _submitForm() {
// 直接使用状态,数据保证是最新的
final userData = {
'name': _name,
'email': _email,
'subscribe': _subscribe,
};
print('提交数据: $userData');
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextFieldFormItem(
label: '姓名',
value: _name,
onChanged: (value) => setState(() => _name = value),
),
TextFieldFormItem(
label: '邮箱',
value: _email,
onChanged: (value) => setState(() => _email = value),
),
SwitchFormItem(
label: '订阅通知',
value: _subscribe,
onChanged: (value) => setState(() => _subscribe = value),
),
ElevatedButton(
onPressed: _submitForm,
child: Text('提交'),
),
],
);
}
}
非受控组件方式(不推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class UserForm extends StatefulWidget {
@override
State<UserForm> createState() => _UserFormState();
}
class _UserFormState extends State<UserForm> {
// 需要维护两套状态
String _name = '';
String _email = '';
bool _subscribe = false;
// 还需要引用子组件来获取值
final _nameFieldKey = GlobalKey<_UncontrolledTextFieldState>();
final _emailFieldKey = GlobalKey<_UncontrolledTextFieldState>();
final _subscribeSwitchKey = GlobalKey<_UncontrolledSwitchState>();
void _submitForm() {
// 需要通过各种方式获取子组件的状态
final name = _nameFieldKey.currentState?._controller.text ?? '';
final email = _emailFieldKey.currentState?._controller.text ?? '';
final subscribe = _subscribeSwitchKey.currentState?._value ?? false;
final userData = {
'name': name,
'email': email,
'subscribe': subscribe,
};
print('提交数据: $userData');
}
@override
Widget build(BuildContext context) {
return Column(
children: [
UncontrolledTextField(
key: _nameFieldKey,
initialValue: _name,
onChanged: (value) => setState(() => _name = value),
),
// ... 其他类似
ElevatedButton(
onPressed: _submitForm,
child: Text('提交'),
),
],
);
}
}
📝 总结
| 方面 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据一致性 | ✅ 保证同步 | ❌ 可能不同步 |
| 调试难度 | ✅ 容易 | ❌ 困难 |
| 代码复杂度 | ✅ 简单清晰 | ❌ 复杂混乱 |
| 可测试性 | ✅ 容易测试 | ❌ 测试困难 |
| 推荐程度 | ⭐⭐⭐⭐⭐ | ⭐☆ |
建议:在 Flutter 开发中,除非有特殊需求,否则始终使用受控组件。这样可以让你的应用数据流更清晰、更易于维护和调试。
🚀 最佳实践
- 状态提升:将状态提升到最近的共同祖先
- 单一数据源:每个状态只有一个”真相来源”
- 不可变数据:使用
setState创建新状态而不是修改旧状态 - 明确的数据流:数据向下流动,事件向上传递
记住这个口诀:"状态上提,数据下传,事件回调"
本文由作者按照 CC BY 4.0 进行授权