一、riverpod状态管理中所涉及到的widget UI组件对比分析
UI 组件 | 状态类型 | 语法形式 | 特点 |
---|---|---|---|
ConsumerWidget | 有状态 | 无状态形式 | 最常用,通过WidgetRef访问provider, 所谓无状态,是指ConsumerWidegt不像StatefulWidegt那样创建state,在它内部不可以定义状态变量,然后再调用setState()更新状态和UI,类似于statelessWidget,但是可以在它内部引用外部的或全局状态提供者provider,以达到全局状态提供者状态更新时,ConsumerWidget也重新构建UI |
ConsumerStatefulWidget | 有状态 | 有状态形式 | 具有完整生命周期,可管理内部状态, 类似于StatefulWidget, 创建状态,重载createState() 初始化状态,重截initState(), 状态销毁,重载dispose() |
Consumer | 有状态 | --- | 局部UI重建,只重建部分UI,优化性能 |
ProviderScope | 有状态 | --- | 创建新的provider作用域,可覆盖父级provider |
HookWidget | 有状态 | 无状态形式 | 使用 Hooks(钩子),依赖flutter_hooks这个库,使用useState 在无状态Widget中管理状态和其他副作用,生命周期使用useEffect |
HookConsumerWidget | 有状态 | 无状态形式 | 可以同时使用 Hooks + Riverpod管理状态,生命周期使用useEffect |
下面用代码分析比较一下使用场景:
1. ConsumerWidget - 最常用的UI组件
final counterProvider = StateProvider<int>((ref) => 0);
class ConsumerWidgetExample extends ConsumerWidget {
const ConsumerWidgetExample({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'1. ConsumerWidget',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
Text('这是一个无状态组件,通过WidgetRef访问provider'),
const SizedBox(height: 10),
Text('计数器: $counter', style: const TextStyle(fontSize: 20)),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: const Text('增加计数'),
),
],
),
),
);
}
}
2. ConsumerStatefulWidget - 需要内部状态的UI组件
final counterProvider = StateProvider<int>((ref) => 0);
class ConsumerStatefulWidgetExample extends ConsumerStatefulWidget {
const ConsumerStatefulWidgetExample({super.key});
@override
ConsumerState<ConsumerStatefulWidgetExample> createState() =>
_ConsumerStatefulWidgetExampleState();
}
class _ConsumerStatefulWidgetExampleState
extends ConsumerState<ConsumerStatefulWidgetExample> {
int _localClicks = 0;
@override
void initState() {
super.initState();
debugPrint('ConsumerStatefulWidget 初始化');
}
@override
void dispose() {
debugPrint('ConsumerStatefulWidget 被销毁');
super.dispose();
}
@override
Widget build(BuildContext context) {
final counter = ref.watch(counterProvider);
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'2. ConsumerStatefulWidget',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
Text('这是一个有状态组件,可以管理内部状态'),
const SizedBox(height: 10),
Text('全局计数器: $counter', style: const TextStyle(fontSize: 16)),
Text('本地点击次数: $_localClicks', style: const TextStyle(fontSize: 16)),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).state++;
},
child: const Text('全局+1'),
),
ElevatedButton(
onPressed: () {
setState(() {
_localClicks++;
});
},
child: const Text('本地+1'),
),
],
),
],
),
),
);
}
}
3. Consumer - 用于局部UI重建
final counterProvider = StateProvider<int>((ref) => 0);
class ConsumerExample extends ConsumerWidget {
const ConsumerExample({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'3. Consumer',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
const Text('使用Consumer只重建UI的特定部分:'),
const SizedBox(height: 10),
// 这个Text不会在计数器变化时重建
const Text('这是静态文本,不会重建'),
const SizedBox(height: 10),
// 只有Consumer内的部分会在计数器变化时重建
Consumer(
builder: (context, ref, child) {
final counter = ref.watch(counterProvider);
return Text(
'动态计数: $counter',
style: const TextStyle(fontSize: 20, color: Colors.blue),
);
},
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: const Text('增加计数'),
),
],
),
),
);
}
}
4. ProviderScope - 用于创建新的provider作用域
ProviderScope示例1
final counterProvider = StateProvider<int>((ref) => 0);
class ProviderScopeExample extends StatelessWidget {
const ProviderScopeExample({super.key});
@override
Widget build(BuildContext context) {
// 创建一个新的provider作用域,可以覆盖父级的provider
return ProviderScope(
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'4. ProviderScope',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
const Text('创建一个新的provider作用域'),
const SizedBox(height: 10),
// 在这个作用域内,可以覆盖父级的provider
Consumer(
builder: (context, ref, child) {
final counter = ref.watch(counterProvider);
return Text('计数器: $counter');
},
),
],
),
),
),
);
}
}
ProviderScope示例2:当我们有一个ListView
显示产品列表,每个项目都需要知道正确的产品ID或索引时:
class ProductItem extends StatelessWidget {
const ProductItem({super.key, required this.index});
final int index;
@override
Widget build(BuildContext context) {
// do something with the index
}
}
class ProductList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (_, index) => ProductItem(index: index),
);
}
}
在上面的代码中,我们将构建器的索引作为构造函数参数传递给 ProductItem
小部件,这种方法有效,但如果ListView
重新构建,它的所有子项也将重新构建。作为替代方法,我们可以在嵌套的ProviderScope
内部覆盖Provider的值:
// 1. Declare a Provider
final currentProductIndex = Provider<int>((_) => throw UnimplementedError());
class ProductList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(itemBuilder: (context, index) {
// 2. Add a parent ProviderScope
return ProviderScope(
overrides: [
// 3. Add a dependency override on the index
currentProductIndex.overrideWithValue(index),
],
// 4. return a **const** ProductItem with no constructor arguments
child: const ProductItem(),
);
});
}
}
class ProductItem extends ConsumerWidget {
const ProductItem({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 5. Access the index via WidgetRef
final index = ref.watch(currentProductIndex);
// do something with the index
}
}
在这种情况下:
- 我们创建一个默认抛出
UnimplementedError
的Provider
。 - 通过将父
ProviderScope
添加到ProductItem
小部件来覆盖其值。 - 我们在
ProductItem
的build
方法中监视索引。
这对性能更有益,因为我们可以将ProductItem
作为const
小部件创建在ListView.builder
中。因此,即使ListView
重新构建,除非其索引发生更改,否则我们的ProductItem
将不会重新构建。
5. HookWidget - 利用hooks钩子在无状态下管理状态
假设你有一个计数器应用,你使用useState
来管理计数值:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class HookWidgetExample extends HookWidget {
const HookWidgetExample({super.key});
@override
Widget build(BuildContext context) {
// 使用 useState Hook 来管理状态
final counter = useState(0);
// 使用 useEffect Hook 处理副作用
useEffect(() {
debugPrint('HookWidget 初始化或计数器变化: ${counter.value}');
return () => debugPrint('HookWidget 清理效果');
}, [counter.value]);
// 使用 useMemoized 缓存计算结果
final doubledValue = useMemoized(() {
return counter.value * 2;
}, [counter.value]);
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'1. HookWidget',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
const Text('表面上是无状态组件,但实际上是有状态的'),
const SizedBox(height: 10),
Text('计数器: ${counter.value}'),
Text('双倍值: $doubledValue'),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () => counter.value++,
child: const Text('增加计数'),
),
],
),
),
);
}
}
6. HookConsumerWidget - 结合 Hooks 和 Riverpod
class HookConsumerWidgetExample extends HookConsumerWidget {
const HookConsumerWidgetExample({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 使用 Hooks 管理本地状态
final localCounter = useState(0);
final animationController = useAnimationController(
duration: const Duration(milliseconds: 500),
);
// 使用 Riverpod 管理全局状态
final globalCounter = ref.watch(counterProvider);
// 使用 useEffect 处理副作用
useEffect(() {
debugPrint('本地计数器变化: ${localCounter.value}');
return null;
}, [localCounter.value]);
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'2. HookConsumerWidget',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
const Text('结合了 Hooks 和 Riverpod 的强大功能'),
const SizedBox(height: 10),
Text('本地计数器: ${localCounter.value}'),
Text('全局计数器: $globalCounter'),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => localCounter.value++,
child: const Text('本地+1'),
),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: const Text('全局+1'),
),
],
),
],
),
),
);
}
}