Files
conti-docs/03-state-management.md
T

124 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 03. 状态管理方案
## 决策
使用 **[Riverpod](https://riverpod.dev/)**`flutter_riverpod` + `riverpod_generator` 代码生成),不使用 Bloc/Provider/GetX。
版本基线:`flutter_riverpod: ^3.4.2`(当前 stable,2026-08 快照,需在实际开工时用 `flutter pub outdated` 复核)。
## 依赖
```yaml
dependencies:
flutter_riverpod: ^3.4.2
riverpod_annotation: ^3.4.2
dev_dependencies:
riverpod_generator: ^3.4.2
build_runner: ^2.4.0
custom_lint: ^0.6.0
riverpod_lint: ^3.0.0
```
## 使用规则
- 所有跨 widget 共享的状态、依赖注入,统一通过 Riverpod provider 暴露,不额外引入 `get_it`/`provider` 等其他 DI 方案。
- 优先使用 `riverpod_generator` 的注解写法(`@riverpod`),不手写裸 `Provider`/`StateNotifierProvider` 模板代码。
- `Notifier`/`AsyncNotifier` 用于承载可变的 feature 状态;无状态的计算/依赖注入用普通 `Provider`
- `domain`/`data` 层的 repository 实现通过 provider 注入到 `presentation` 层,`presentation` 只依赖 provider 暴露的接口类型(见 [02-layering.md](./02-layering.md))。
- 每个 `feature_*` 包各自维护自己的 provider,不跨包直接引用另一个 feature 的 provider(同 [01-project-structure.md](./01-project-structure.md) 的 feature 隔离规则);跨 feature 共享的 provider 定义在对应的 `core_*` 包里。
## 测试
- `Notifier`/`AsyncNotifier` 的单元测试用 `ProviderContainer` 直接实例化,不依赖 widget tree。
- Widget 测试中用 `ProviderScope(overrides: [...])` 注入 mock 依赖。
## 附录:Riverpod 是什么,日常怎么用
给还没接触过 Riverpod 的同学看的入门说明。
### 要解决的问题
Flutter 官方最早推荐的状态管理方式是 [`InheritedWidget`](https://docs.flutter.dev/data-and-backend/state-mgmt/inherited-widget)——通过 widget 树往下传数据。写法繁琐,社区后来做了一层封装叫 [`Provider`](https://pub.dev/packages/provider),但 `Provider` 本质还是绑定在 widget 树上:拿依赖必须要有 `BuildContext`,写错了会在运行时才报错(比如 `ProviderNotFoundException`),而且没法很方便地在 widget 树之外(比如后台任务、单元测试)读取状态。
**Riverpod**`Provider` 的原作者 Remi Rousselet 重新设计的下一代方案:把状态容器从 widget 树里剥离出来,变成一套独立的依赖图,`BuildContext` 不再是拿依赖的必要条件,错误也从运行时提前到**编译期**发现(比如 provider 类型不匹配会直接编译报错,而不是运行时崩溃)。
### 核心概念
1. **`Provider`**:声明一个"如何创建某个值"的配方,值可以是同步的、异步的(`Future`/`Stream`)、也可以是可变的状态。
2. **`Notifier` / `AsyncNotifier`**:承载**可变**状态的载体,通过方法修改状态(类似过去 `StateNotifier` 的角色,3.x 里统一成 `Notifier`)。
3. **`ref.watch(xxxProvider)`**:在 widget 或另一个 provider 里订阅某个 provider,值变化时自动触发重建/重新计算。
4. **`ref.read(xxxProvider)`**:只读取一次当前值,不订阅变化(一般用在按钮点击等一次性事件回调里)。
5. **`@riverpod` 注解 + 代码生成**:手写 `Provider`/`NotifierProvider` 样板代码容易出错(尤其是泛型),项目统一用 `riverpod_generator` 的注解写法,跑 `build_runner` 自动生成对应的 provider。
### 使用示例(`feature_store`:拉取附近门店列表)
```dart
// presentation/store_list_notifier.dart
part 'store_list_notifier.g.dart';
@riverpod
class StoreListNotifier extends _$StoreListNotifier {
@override
Future<List<Store>> build() async {
final repository = ref.watch(storeRepositoryProvider);
return repository.fetchNearbyStores(_currentLat, _currentLng);
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() => build());
}
}
```
```dart
// presentation/store_list_page.dart
class StoreListPage extends ConsumerWidget {
const StoreListPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final storesAsync = ref.watch(storeListNotifierProvider);
return storesAsync.when(
data: (stores) => ListView.builder(
itemCount: stores.length,
itemBuilder: (_, i) => ListTile(title: Text(stores[i].name)),
),
loading: () => const CircularProgressIndicator(),
error: (err, _) => Text('加载失败: $err'),
);
}
}
```
`storeRepositoryProvider` 定义在 `data` 层(见 [02-layering.md](./02-layering.md) 的跳过 domain 层示例),`StoreListNotifier` 通过 `ref.watch` 拿到接口类型,不关心具体实现——这就是 Riverpod 承担依赖注入职责的地方,不需要额外的 `get_it`
### 测试示例
```dart
test('刷新后状态应更新为最新门店列表', () async {
final container = ProviderContainer(
overrides: [
storeRepositoryProvider.overrideWithValue(FakeStoreRepository()),
],
);
addTearDown(container.dispose);
final stores = await container.read(storeListNotifierProvider.future);
expect(stores, isNotEmpty);
});
```
`ProviderContainer` 让整个依赖图脱离 widget 树单独运行,`overrides` 直接替换掉真实的 repository,这也是"编译期安全 + 好测试"这条评价的具体体现。
## 参考链接
- [Riverpod 官方文档](https://riverpod.dev/)
- [riverpod_generator | Dart package](https://pub.dev/packages/riverpod_generator)
- [flutter_riverpod | Dart package](https://pub.dev/packages/flutter_riverpod)
- [riverpod_lint | Dart package](https://pub.dev/packages/riverpod_lint)
- [InheritedWidgetFlutter 官方文档)](https://docs.flutter.dev/data-and-backend/state-mgmt/inherited-widget)
- [provider | Dart package](https://pub.dev/packages/provider)