把状态保存在共同的祖先中,并把你的子 widget 变成受控 widget,让它们通过回调把变化上报回来。
在 Kodokon 中打开本课一个经典的场景:一个数量选择器和显示在别处的总价需要共享同一份数据。黄金法则:状态存放在依赖它的那些 widget 最近的共同祖先中。父组件拥有数据,把它作为参数往下传,并通过一个向上回流的回调来接收变化。这就是 "lifting state up"(状态提升)。
import 'package:flutter/material.dart';
class CartScreen extends StatefulWidget {
const CartScreen({super.key});
@override
State<CartScreen> createState() => _CartScreenState();
}
class _CartScreenState extends State<CartScreen> {
int _quantity = 1;
void _updateQuantity(int value) {
setState(() => _quantity = value);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Cart')),
body: Column(
children: [
QuantitySelector(
quantity: _quantity,
onChanged: _updateQuantity,
),
Text('Total: ${_quantity * 20} €'),
],
),
);
}
}子组件成了一个受控 widget:它是一个 StatelessWidget,不保存任何东西,显示它收到的值,并通过 onChanged 发出每一个意图。ValueChanged<int> 只不过是 void Function(int) 的别名而已。注意在数量为 1 时被禁用的减号按钮:只要给 onPressed 传入 null 就够了,而这个逻辑直接由收到的值推导而来。
import 'package:flutter/material.dart';
class QuantitySelector extends StatelessWidget {
const QuantitySelector({
super.key,
required this.quantity,
required this.onChanged,
});
final int quantity;
final ValueChanged<int> onChanged;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: quantity > 1
? () => onChanged(quantity - 1)
: null,
),
Text('$quantity'),
IconButton(
icon: const Icon(Icons.add),
onPressed: () => onChanged(quantity + 1),
),
],
);
}
}你其实一直在不知不觉地使用这个约定:Checkbox、Slider、Switch 都是受控 widget(value + onChanged)。当树变得越来越深,把回调层层穿过五层变得令人厌烦时,就该去认识状态管理方案了 - 那正是下一单元的主题。