## 前言
Riverpod 是 Flutter 下知名度较高的状态管理依赖,同样出自 Provider 的开发者 rrousselGit 之手。
其实仔细去看 Riverpod 似乎只是 Provider 的拼写打乱了顺序,其提供了更简洁的API 设计,实现了依赖注入。
如果去看过 rrousselGit 的主页,你可以发现,他也是著名的 Flutter_hooks 的作者,RiverPod 也理所当然的拥有 hook 相关的血统 > `HookConsumerWidget`, 我们可以在享受 hooks 的同时,直接使用`Widget.ref(provider).watch` 来监听变更并自动刷新页面。
## 为什么 Flutter 需要状态管理
Flutter 作为优秀的跨端框架,其使用的声明式UI有诸多优势,但嵌套的组件给数据传递带来了极大的挑战。
如果将数据在 组件类的构造函数中携带,并在数层中进行传递,随着代码量的提升,将会极大的增加代码的复杂和易理解程度。
因此状态管理组件出现了,其提供了一个清晰的模型来管理数据流,确保数据在正确的时机以正确的方式流动。这有助于避免数据不一致和难以追踪的 bug。通过集中的状态管理,我们可以更加容易的理解和增删需要传递的数据。
### 举个例子
我们可以使用最常见的 Flutter demo 来看, 在初始化完成项目之后,我们便可以看到这个例子
```dart
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter ;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
```
因为 需要渲染的页面和按钮在 同一个组件 `MyHomePage` 下 因此我们可以很简单的在按钮点击的同时 setState 来使Flutter 感知数据的变化 并重新渲染页面。
#### 组件分离
但是 多数情况下, 我们需要渲染的页面,和改变数据的按钮 并不在一个组件中,例如,如果我们将这个按钮单独封装在一个类中。这种情况下,我们应该如何在点击按钮的时候增加数据呢?
```dart
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${_counter._count}',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: const Button(),
);
}
}
class Button extends StatelessWidget {
const Button({super.key});
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: _counter.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
);
}
}
```
#### 使用 ChangeNotify
ChangeNotify 是 Flutter 下用于监听数据变更的组件,在这个场景下,我们可以使用其监听 counter 的变化, 并重新渲染页面
```dart
class Counter extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count ;
notifyListeners();
}
}
Counter _counter = Counter();
......
class _MyHomePageState extends State<MyHomePage> {
@override
void dispose() {
_counter.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_counter.addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${_counter._count}',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: const Button(),
);
}
}
......
```
#### 使用 RiverPod
再简单场景下,`ChangeNotify` 和 `ValueNotify` 已经能够胜任, 但对于复杂场景,还是有一定的局限性。
先添加如下依赖 (这里使用 Flutter_hooks 举例)
```yaml
dependencies:
flutter_hooks: ^0.20.5
hooks_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
dev_dependencies:
build_runner: ^2.4.11
riverpod_generator: ^2.4.0
```
别忘记在最外围增加一个 `ProvideScope`
```dart
void main() {
runApp(const ProviderScope(child: MyApp()));
}
```
接着新增一个 `counter_provider.dart` 文件
```dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter_provider.g.dart';
@Riverpod(keepAlive: true)
class Counter extends _$Counter {
@override
int build() => 0;
void increment() {
state ;
}
}
```
运行 代码生成
```shell
$ dart run build_runner build
```
他将会生成一个 `counter_provider.g.dart` 文件
```dart
// 接着我们可以使用 `ref.watch` 来监听数据的变化
ref.watch(counterProvider);
// 使用 ref.read().increment() 增加1
ref.read(counterProvider.notifier).increment;
```
完整的例子:
```dart
import 'package:daka/counter_provider.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends HookConsumerWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
ref.watch(counterProvider).toString(),
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: const Button(),
);
}
}
class Button extends HookConsumerWidget {
const Button({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.read(counterProvider.notifier);
return FloatingActionButton(
onPressed: counter.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
);
}
}
```