特点 | Riverpod(魔法盒子) | Bloc(邮递员) |
---|
怎么用 | 直接开盒子拿东西 | 写信 → 等邮递员回信 |
适合场景 | 小玩具共享(简单数据) | 复杂任务(比如买冰淇淋要付钱、找零) |
要写很多代码吗 | 很少!盒子声明+拿取就行 | 要多写“事件”和“状态”类(邮递员很严谨) |
谁推荐用 | 想快速做App的开发者 | 做大项目、怕逻辑乱的团队 |
❓选哪个好?
- 想快一点、简单点:用
Riverpod
(魔法盒子超方便!) - 做超复杂App(比如游戏、购物车):用
Bloc
(邮递员不会送错信)
🌟 超简单对比表(小学生版)
对比项 | 普通代码 | Riverpod(魔法盒子) |
---|
存状态的地方 | 藏在页面里(容易丢)📦 | 放独立盒子(安全!)🔒 |
拿状态 | 只能本页用(传纸条麻烦)📝 | 全App随便开盒子(超方便)🗝️ |
改数字后 | 整个页面闪一下(费电)💥 | 只变数字(其他部分不动)🎯 |
加新功能 | 要改一堆代码(容易乱)🌀 | 新盒子单独写(旧代码不动)✨ |
适合谁用 | 临时小玩具(比如算1+1)🧸 | 做大作业(比如班级管理系统)🏫 |
🧩 1. 普通代码:直接写逻辑(像在草稿本上乱画)
class NormalCounterPage extends StatefulWidget {const NormalCounterPage({super.key});@overrideState<NormalCounterPage> createState() => _NormalCounterPageState();
}class _NormalCounterPageState extends State<NormalCounterPage> {int count = 0; // 状态直接写在页面里(像草稿本上写数字)void increment() {setState(() { // 必须手动通知刷新count++; });}@overrideWidget build(BuildContext context) {return Scaffold(body: Center(child: Text("计数: $count")),floatingActionButton: FloatingActionButton(onPressed: increment, // 点按钮 → 数字+1child: Icon(Icons.add),),);}
}
问题 | 说明 |
---|
状态和UI绑死 | 状态(count )和页面代码混在一起,搬家难 |
跨页面共享难 | 其他页面想用这个数字?得一层层传过去(像传纸条) |
刷新范围太大 | setState 会让整个页面刷新(浪费电⚡) |
测试困难 | 想测试数字会不会变?得把整个页面建起来 |
🎁 2. Riverpod:用魔法盒子装状态(像存钱罐)
// ✅ 第一步:创建“存钱罐”(Provider)
final countProvider = StateProvider((ref) => 0); // 罐子名字叫 countProvider,里面放数字0// ✅ 第二步:在页面里取钱(读状态)
class RiverpodCounterPage extends ConsumerWidget { // 注意这里是 ConsumerWidget!const RiverpodCounterPage({super.key});@overrideWidget build(BuildContext context, WidgetRef ref) {// 打开存钱罐看余额 👇final count = ref.watch(countProvider); // 自动监听变化return Scaffold(body: Center(child: Text("计数: $count")),floatingActionButton: FloatingActionButton(onPressed: () {// 往罐子里投硬币 👇ref.read(countProvider.notifier).state++; // 数字+1},child: Icon(Icons.add),),);}
}
优点 | 说明 |
---|
状态和UI分开 | 数字存在独立“盒子”里,页面只负责显示 |
全局共享 | 其他页面直接开盒子拿数字(不用传纸条) |
精准刷新 | 只有 Text("计数: $count") 会刷新(省电⚡) |
测试超简单 | 不用建页面,直接测试盒子里的逻辑 |
Riverpod 库核心方法和类的表格总结
1. Provider 类型
方法 / 类 | 描述 | 适用场景 |
---|
Provider | 提供不可变数据或纯函数,无状态。 | 常量、工具类、配置 |
StateProvider | 管理简单可变状态(如布尔值、计数器)。 | 局部 UI 状态(如复选框、加载状态) |
StateNotifierProvider | 使用StateNotifier 管理复杂状态逻辑(如列表操作、异步状态)。 | 复杂业务逻辑(如待办事项管理) |
FutureProvider | 处理异步操作,自动管理加载 / 错误 / 数据状态。 | 网络请求、数据库查询 |
StreamProvider | 监听数据流(如 WebSocket、Firebase 实时数据)。 | 实时更新(如聊天消息、传感器数据) |
Provider.family | 参数化 Provider,根据传入参数返回不同状态。 | 根据 ID 获取用户数据等场景 |
2. 状态读取与修改
方法 | 描述 | 示例代码 |
---|
ref.watch | 监听 Provider 变化,Widget 重建时自动更新。 | final count = ref.watch(counterProvider); |
ref.read | 一次性读取 Provider 值(不监听变化),常用于事件处理。 | ref.read(counterProvider.notifier).state++; |
ref.listen | 监听 Provider 变化但不重建 Widget,适合触发副作用(如导航、显示 SnackBar)。 | ref.listen(authProvider, (prev, curr) { if (curr.isLoggedIn) {...} }); |
ref.refresh | 强制重新计算 Provider(如刷新网络数据)。 | ref.refresh(userDataProvider); |
两个页面要共享状态案例
🧩 第一步:创建共享的“糖果罐”(Provider)
// 在全局创建(任何页面都能用)
final candyProvider = StateProvider<int>((ref) => 5); // 罐子叫 candyProvider,初始放5颗糖
📱 第二步:两个页面共用这个罐子
页面1:显示糖果数 + 增加糖果
class Page1 extends ConsumerWidget {@overrideWidget build(BuildContext context, WidgetRef ref) {final candies = ref.watch(candyProvider); // 盯着罐子里的糖return Scaffold(body: Center(child: Text("页面1: 有 $candies 颗糖🍬", style: TextStyle(fontSize: 24)),),floatingActionButton: FloatingActionButton(onPressed: () => ref.read(candyProvider.notifier).state++, // 点按钮 → 糖+1child: Icon(Icons.add),),);}
}
页面2:只显示糖果数(糖变少这里自动刷新)
class Page2 extends ConsumerWidget {@overrideWidget build(BuildContext context, WidgetRef ref) {final candies = ref.watch(candyProvider); // 也盯着同一个罐子return Scaffold(body: Center(child: Text("页面2: 看到 $candies 颗糖🍬", style: TextStyle(fontSize: 24)),),);}
}
🌐 第三步:主入口(跳转两个页面)
void main() {runApp(ProviderScope( // ✅ 整个App套上这个(像给App装电池)child: MaterialApp(home: Scaffold(body: Center(children: [ElevatedButton(onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => Page1())),child: Text("去页面1"),),ElevatedButton(onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => Page2())),child: Text("去页面2"),),],),),),),);
}
💡 为什么这样好用?
场景 | 普通写法 | Riverpod(糖果罐) |
---|
页面1改数据 | 页面2看不到变化 😭 | 页面2自动刷新 → 糖数永远最新 ✅ |
新增页面3 | 要手动传数据 📦 | 直接开罐子拿糖(不用传)🗝️ |
关掉所有页面 | 糖果数据消失 🚫 | 罐子还在!下次打开还是最新数 🔋 |