本博客的参考文章:7.3 跨组件状态共享 | 《Flutter实战·第二版》
通过前一篇文章: Flutter InheritedWidget 详解:从生命周期到数据流动的完整解析,我们已经深入理解了 InheritedWidget
的工作原理,现在让我们基于这些知识来实现一个完整的 Provider 模式。这将帮助我们解决实际开发中的状态管理问题。
现有方案的问题
在前面的例子中,我们发现了一个性能问题:每次调用 setState()
时,整个组件树都会重建,即使某些子组件并不依赖变化的数据。
// 问题代码示例
class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
int count = 0;
Widget build(BuildContext context) {
print("😱 整个页面都在重建!");
return ShareDataWidget(
data: count,
child: Column(
children: [
TestWidget(), // 依赖数据,需要更新
StaticWidget(), // 不依赖数据,但也被重建了!
AnotherWidget(), // 不依赖数据,但也被重建了!
],
),
);
}
}
解决方案:缓存 + Provider 模式
我们需要实现一个智能的状态管理方案,它应该具备以下特性:
- 精确更新:只更新真正依赖数据的组件
- 缓存机制:避免不必要的重建
- 易于使用:提供简洁的 API
- 类型安全:支持泛型,避免类型错误
实现 ChangeNotifier:可观察的数据模型
首先,我们需要一个可以通知变化的数据模型:
// 抽象的变化通知器
abstract class ChangeNotifier {
List<VoidCallback> _listeners = [];
// 添加监听器
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
// 移除监听器
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
// 通知所有监听器
void notifyListeners() {
for (final listener in _listeners) {
listener();
}
}
// 销毁时清理监听器
void dispose() {
_listeners.clear();
}
}
// 具体的计数器模型
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
print("📊 CounterModel: count 增加到 $_count,通知监听器");
notifyListeners(); // 通知所有监听器数据已变化
}
void decrement() {
_count--;
print("📊 CounterModel: count 减少到 $_count,通知监听器");
notifyListeners();
}
}
实现 ChangeNotifierProvider:智能的数据提供者
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
final T Function() create; // 创建数据模型的工厂函数
final Widget child;
final bool lazy; // 是否懒加载
const ChangeNotifierProvider({
Key? key,
required this.create,
required this.child,
this.lazy = true,
}) : super(key: key);
// 便捷方法:获取数据模型
static T of<T extends ChangeNotifier>(BuildContext context, {bool listen = true}) {
if (listen) {
// 建立依赖关系,当数据变化时会重建
return context.dependOnInheritedWidgetOfExactType<_InheritedProvider<T>>()!.model;
} else {
// 不建立依赖关系,仅获取数据
return context.getElementForInheritedWidgetOfExactType<_InheritedProvider<T>>()!.widget.model;
}
}
_ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>();
}
class _ChangeNotifierProviderState<T extends ChangeNotifier>
extends State<ChangeNotifierProvider<T>> {
T? _model;
void initState() {
super.initState();
print("🏗️ ChangeNotifierProvider initState");
if (!widget.lazy) {
_createModel();
}
}
void _createModel() {
if (_model == null) {
print("🔧 创建数据模型 ${T.toString()}");
_model = widget.create();
_model!.addListener(_onModelChanged);
}
}
void _onModelChanged() {
print("📢 数据模型变化,触发 setState");
// 当模型数据变化时,重建 InheritedWidget
setState(() {});
}
Widget build(BuildContext context) {
print("🎨 ChangeNotifierProvider build");
if (widget.lazy && _model == null) {
_createModel();
}
return _InheritedProvider<T>(
model: _model!,
child: widget.child,
);
}
void dispose() {
print("🗑️ ChangeNotifierProvider dispose");
_model?.removeListener(_onModelChanged);
_model?.dispose();
super.dispose();
}
}
// 内部使用的 InheritedWidget
class _InheritedProvider<T extends ChangeNotifier> extends InheritedWidget {
final T model;
const _InheritedProvider({
Key? key,
required this.model,
required Widget child,
}) : super(key: key, child: child);
bool updateShouldNotify(_InheritedProvider<T> oldWidget) {
// 这里我们总是返回 true,因为 ChangeNotifier 已经确保只有在真正变化时才会触发重建
bool shouldNotify = oldWidget.model != model;
print("⚖️ _InheritedProvider updateShouldNotify: $shouldNotify");
return shouldNotify;
}
}
实现 Consumer:智能的数据消费者
class Consumer<T extends ChangeNotifier> extends StatelessWidget {
final Widget Function(BuildContext context, T model, Widget? child) builder;
final Widget? child; // 不需要重建的子组件,用于性能优化
const Consumer({
Key? key,
required this.builder,
this.child,
}) : super(key: key);
Widget build(BuildContext context) {
print("🎯 Consumer<${T.toString()}> build");
// 获取数据模型并建立依赖关系
final model = ChangeNotifierProvider.of<T>(context, listen: true);
return builder(context, model, child);
}
}
// 不监听变化的 Consumer,用于性能优化
class ConsumerWidget<T extends ChangeNotifier> extends StatelessWidget {
final Widget Function(BuildContext context, T model) builder;
const ConsumerWidget({
Key? key,
required this.builder,
}) : super(key: key);
Widget build(BuildContext context) {
print("🎯 ConsumerWidget<${T.toString()}> build (不监听变化)");
// 获取数据模型但不建立依赖关系
final model = ChangeNotifierProvider.of<T>(context, listen: false);
return builder(context, model);
}
}
完整的使用示例
现在让我们用新的 Provider 模式重写计数器应用:
class ProviderDemoApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Provider 模式演示',
home: ChangeNotifierProvider<CounterModel>(
create: () => CounterModel(),
child: CounterHomePage(),
),
);
}
}
class CounterHomePage extends StatelessWidget {
Widget build(BuildContext context) {
print("🏠 CounterHomePage build - 这个页面不会因为计数变化而重建!");
return Scaffold(
appBar: AppBar(
title: Text('Provider 计数器'),
),
body: Column(
children: [
// 显示计数的组件 - 会监听变化
CounterDisplay(),
// 静态组件 - 不会重建
StaticInfoWidget(),
// 操作按钮区域
CounterControls(),
// 另一个显示计数的组件
AnotherCounterDisplay(),
],
),
);
}
}
// 显示计数的组件 - 使用 Consumer 监听变化
class CounterDisplay extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counter, child) {
print("🔢 CounterDisplay 重建,新的计数值: ${counter.count}");
return Container(
padding: EdgeInsets.all(20),
margin: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.blue),
),
child: Column(
children: [
Text(
'当前计数',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
Text(
'${counter.count}',
style: TextStyle(fontSize: 48, color: Colors.blue),
),
],
),
);
},
);
}
}
// 静态组件 - 不依赖数据,不会重建
class StaticInfoWidget extends StatelessWidget {
Widget build(BuildContext context) {
print("ℹ️ StaticInfoWidget build - 我不会因为计数变化而重建");
return Container(
padding: EdgeInsets.all(15),
margin: EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(Icons.info, color: Colors.grey),
SizedBox(width: 10),
Text(
'这是一个静态组件,不会因为计数变化而重建',
style: TextStyle(color: Colors.grey.shade700),
),
],
),
);
}
}
// 操作按钮组件
class CounterControls extends StatelessWidget {
Widget build(BuildContext context) {
print("🎮 CounterControls build");
return Container(
padding: EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 减少按钮 - 不监听变化,仅获取模型来调用方法
ConsumerWidget<CounterModel>(
builder: (context, counter) {
return ElevatedButton.icon(
onPressed: counter.decrement,
icon: Icon(Icons.remove),
label: Text('减少'),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
);
},
),
// 增加按钮 - 不监听变化,仅获取模型来调用方法
ConsumerWidget<CounterModel>(
builder: (context, counter) {
return ElevatedButton.icon(
onPressed: counter.increment,
icon: Icon(Icons.add),
label: Text('增加'),
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
);
},
),
],
),
);
}
}
// 另一个显示计数的组件 - 演示多个组件可以同时监听同一个数据
class AnotherCounterDisplay extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<CounterModel>(
// 使用 child 参数优化性能 - 不变的部分不会重建
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.orange.shade100,
borderRadius: BorderRadius.circular(5),
),
child: Text(
'我也在监听计数变化',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
builder: (context, counter, child) {
print("🔢 AnotherCounterDisplay 重建,计数值: ${counter.count}");
return Container(
margin: EdgeInsets.all(20),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.orange),
),
child: Column(
children: [
child!, // 这部分不会重建
SizedBox(height: 10),
Text(
'计数的平方: ${counter.count * counter.count}',
style: TextStyle(fontSize: 20, color: Colors.orange.shade800),
),
],
),
);
},
);
}
}
执行流程分析
让我们看看点击"增加"按钮时的完整执行流程:
1. 用户点击按钮
🎮 用户点击"增加"按钮
↓
📊 CounterModel.increment() 被调用
↓
📊 CounterModel: count 增加到 1,通知监听器
↓
📢 _onModelChanged() 被调用
↓
📢 数据模型变化,触发 setState
2. Provider 重建流程
🎨 ChangeNotifierProvider build
↓
⚖️ _InheritedProvider updateShouldNotify: true
↓
📢 通知所有依赖的子组件
3. 子组件更新流程
🔢 CounterDisplay didChangeDependencies (如果是首次)
↓
🔢 CounterDisplay 重建,新的计数值: 1
↓
🔢 AnotherCounterDisplay didChangeDependencies (如果是首次)
↓
🔢 AnotherCounterDisplay 重建,计数值: 1
注意:StaticInfoWidget
和 CounterHomePage
不会重建!
性能优化技巧
1. 使用 child 参数避免不必要的重建
Consumer<CounterModel>(
// 这部分不会因为数据变化而重建
child: ExpensiveWidget(),
builder: (context, counter, child) {
return Column(
children: [
Text('计数: ${counter.count}'), // 会重建
child!, // 不会重建
],
);
},
)
2. 选择性监听
class SmartWidget extends StatelessWidget {
Widget build(BuildContext context) {
// 只获取数据,不监听变化
final counter = ChangeNotifierProvider.of<CounterModel>(context, listen: false);
return ElevatedButton(
onPressed: counter.increment, // 只是调用方法,不需要监听数据变化
child: Text('增加'),
);
}
}
3. 多个 Provider 的组合使用
class MultiProviderApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider<CounterModel>(
create: () => CounterModel(),
child: ChangeNotifierProvider<UserModel>(
create: () => UserModel(),
child: ChangeNotifierProvider<ThemeModel>(
create: () => ThemeModel(),
child: MyHomePage(),
),
),
),
);
}
}
// 在子组件中使用多个 Provider
class MultiConsumerWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counter, _) {
return Consumer<UserModel>(
builder: (context, user, _) {
return Text('${user.name}: ${counter.count}');
},
);
},
);
}
}
与原生 Provider 包的对比
我们实现的简化版 Provider 具备了核心功能:
功能 | 我们的实现 | Provider 包 |
---|---|---|
基本状态管理 | ✅ | ✅ |
精确更新 | ✅ | ✅ |
类型安全 | ✅ | ✅ |
性能优化 | ✅ | ✅ |
多 Provider 支持 | ✅ | ✅ |
Selector 优化 | ❌ | ✅ |
ProxyProvider | ❌ | ✅ |
更多便捷 API | ❌ | ✅ |
总结
通过实现这个 Provider 模式,我们深入理解了:
- InheritedWidget 的实际应用:如何将理论知识转化为实用的状态管理方案
- 缓存机制的重要性:避免不必要的组件重建
- 观察者模式:
ChangeNotifier
如何通知数据变化 - 性能优化策略:选择性监听、子组件缓存等技巧