Riverpod 2.x 带来了重大的 API 改进和重构,其中最显著的变化是 StateNotifierProvider 的弃用以及 Widget 使用方式的更新。本文将详细解析这些变化,帮助你平滑迁移到新版 Riverpod。
核心变化一览
🔄 Provider 类型的演变
旧版本 (v1.x) 新版本 (v2.x) 用途
StateNotifierProvider 已弃用 复杂同步状态管理
- NotifierProvider 同步复杂状态管理
- AsyncNotifierProvider 异步复杂状态管理
StateProvider StateProvider 简单状态管理
FutureProvider FutureProvider 异步数据获取
StreamProvider StreamProvider 实时数据流
🎯 Notifier 类的重写
旧方式(已弃用):
dart
class CounterStateNotifier extends StateNotifier<int> {
CounterStateNotifier() : super(0);
void increment() => state++;
}
新方式:
dart
class CounterNotifier extends Notifier<int> {
@override
int build() => 0; // 同步初始化
void increment() => state = state + 1;
}
异步版本:
dart
class UserNotifier extends AsyncNotifier<User> {
@override
Future<User> build() async {
return await fetchUser(); // 异步初始化
}
Future<void> updateUser() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
return await api.updateUser();
});
}
}
🏗️ Widget API 的现代化
ConsumerWidget 的变化:
dart
// BEFORE (v1.x)
class OldWidget extends ConsumerWidget {
Widget build(context, ScopedReader watch) {
final count = watch(counterProvider);
return Text('$count');
}
}
// AFTER (v2.x)
class NewWidget extends ConsumerWidget {
Widget build(context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}
关键变化:
· ScopedReader watch → WidgetRef ref
· watch(provider) → ref.watch(provider)
🛠️ 实战:创建现代 Riverpod 应用
第 1 步:定义 Provider
dart
// 简单状态
final themeProvider = StateProvider<bool>((ref) => false);
// 复杂同步状态
final todoProvider = NotifierProvider<TodoNotifier, List<Todo>>(
() => TodoNotifier()
);
// 复杂异步状态
final userProvider = AsyncNotifierProvider<UserNotifier, User>(
() => UserNotifier()
);
第 2 步:实现 Notifier
dart
// 同步 Notifier
class TodoNotifier extends Notifier<List<Todo>> {
@override
List<Todo> build() => [];
void addTodo(String title) {
final newTodo = Todo(
id: DateTime.now().toString(),
title: title,
completed: false,
);
state = [...state, newTodo];
}
void toggleTodo(String id) {
state = state.map((todo) {
return todo.id == id
? todo.copyWith(completed: !todo.completed)
: todo;
}).toList();
}
}
// 异步 Notifier
class UserNotifier extends AsyncNotifier<User> {
@override
Future<User> build() async {
return await UserRepository().getCurrentUser();
}
Future<void> updateName(String newName) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
final updated = await api.updateProfile(name: newName);
return updated;
});
}
}
第 3 步:在 Widget 中使用
dart
class TodoScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 监听变化
ref.listen(todoProvider, (previous, next) {
if (next.length >= 10) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已经有 ${next.length} 个待办事项'))
);
}
});
final todos = ref.watch(todoProvider);
final isLoading = ref.watch(userProvider.select((u) => u.isLoading));
return Scaffold(
appBar: AppBar(
title: Consumer(
builder: (context, ref, child) {
final userName = ref.watch(
userProvider.select(
(u) => u.when(
data: (user) => user.name,
loading: () => '加载中...',
error: (_, __) => '错误',
)
)
);
return Text('欢迎, $userName');
},
),
),
body: Column(
children: [
if (isLoading) LinearProgressIndicator(),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
title: Text(todo.title),
leading: Checkbox(
value: todo.completed,
onChanged: (_) {
ref.read(todoProvider.notifier).toggleTodo(todo.id);
},
),
);
},
),
),
TodoInput(),
],
),
);
}
}
第 4 步:性能优化
dart
// 使用 select 精确控制重建范围
class TodoStats extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 只有 completedCount 变化时才重建
final completedCount = ref.watch(
todoProvider.select((todos) =>
todos.where((t) => t.completed).length
)
);
final totalCount = ref.watch(
todoProvider.select((todos) => todos.length)
);
return Text('完成: $completedCount / 总计: $totalCount');
}
}
🚀 迁移指南
自动迁移步骤
- 更新依赖:
yaml
dependencies:
flutter_riverpod: ^2.0.0
- 全局替换:
· watch( → ref.watch(
· read( → ref.read(
· ConsumerStatefulWidget 中的 watch 方法改为 ref.watch - 重写 StateNotifier:
dart
// BEFORE
class OldNotifier extends StateNotifier<StateType> {
OldNotifier() : super(initialState);
void method() => state = newState;
}
// AFTER
class NewNotifier extends Notifier<StateType> {
@override
StateType build() => initialState;
void method() => state = newState;
}
手动检查清单
· 更新所有 ConsumerWidget 的 build 方法签名
· 将 StateNotifierProvider 替换为 NotifierProvider 或 AsyncNotifierProvider
· 更新 StateNotifier 为 Notifier/AsyncNotifier
· 替换 ProviderListener 为 ref.listen
· 检查 select 方法的使用
· 更新异步状态处理逻辑
💡 最佳实践
- Provider 组织
dart
// providers/
// ├── todo_provider.dart
// ├── user_provider.dart
// └── theme_provider.dart
- 状态不可变性
dart
void updateUser(User newUser) {
// ✅ 正确:创建新对象
state = state.copyWith(
name: newUser.name,
email: newUser.email,
);
// ❌ 错误:直接修改
// state.name = newUser.name;
}
- 错误处理
dart
Future<void> fetchData() async {
try {
state = const AsyncLoading();
final data = await repository.fetch();
state = AsyncData(data);
} catch (e, st) {
state = AsyncError(e, st);
rethrow;
}
}
- 测试友好
dart
test('todo notifier adds todo', () async {
final container = ProviderContainer();
final notifier = container.read(todoProvider.notifier);
notifier.addTodo('测试任务');
expect(
container.read(todoProvider),
contains(predicate((todo) => todo.title == '测试任务'))
);
});
📊 性能对比
特性 v1.x v2.x 改进
类型安全 ✅ ✅✅ 更好的泛型支持
异步处理 🔄 🚀 AsyncNotifier 简化
代码简洁性 ⭐⭐⭐ ⭐⭐⭐⭐⭐ build() 方法更直观
迁移成本 - 中等 需要重写 Notifier
学习曲线 平缓 更平缓 统一的使用模式
🎉 结论
Riverpod 2.x 通过以下改进提供了更好的开发体验:
- 更统一的 API:所有 Provider 都使用 ref.watch/ref.read
- 更好的类型推断:减少了泛型模板代码
- 简化的异步处理:AsyncNotifier 内置状态管理
- 改进的性能:select 方法减少不必要的重建
- 更好的可测试性:Provider 容器更容易 mock
虽然迁移需要一些工作,但新 API 的简洁性和强大功能使得这项工作非常值得。开始迁移到 Riverpod 2.x,享受现代化状态管理带来的便利吧!
进一步阅读:
· Riverpod 官方文档
· Riverpod 2.0 迁移指南
· GitHub 示例项目
标签: #Flutter #Riverpod #状态管理 #Dart #移动开发