Flutter Riverpod 2.0中.select()无法阻止不必要重建的问题
误区1:认为select能阻止父Widget重建
你的核心问题在于把两个ref.watch都放在了TestWidget的build方法里。ConsumerWidget的特性是:只要它监听的任何一个provider发生变化(且监听条件满足),整个build方法就会重新执行。
也就是说,当你点击Inc 1触发count1更新时,第一个ref.watch的select条件满足(值变化),会触发TestWidget的build重新运行。此时第二个ref.watch也会被执行一遍,导致你看到第二个Text的print语句被调用——哪怕它的select返回值没有变化。
你预期的“仅第一个Text重建”需要让每个Text的监听独立于父Widget,方法是把每个Text包裹在单独的Consumer中,或者拆成独立的ConsumerWidget,这样只有当自身监听的select值变化时,才会触发局部重建,不会影响其他部分。
误区2:未正确处理状态类的相等性判断
你的Counter类没有重写==运算符和hashCode方法,Riverpod默认通过引用相等来判断状态是否变化。每次调用inc1或inc2时,你都创建了一个新的Counter实例,哪怕其中一个字段的值没有变化,Riverpod也会认为状态发生了变化,进而通知所有监听该provider的地方。
虽然这不是第二个Text触发print的直接原因,但会导致不必要的状态通知,也是你看到重复打印的潜在因素之一。
解决方法
1. 重构Widget结构,实现局部监听
把每个Text用Consumer包裹,让它们的监听独立:
class TestWidget extends StatelessWidget { const TestWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ Consumer( builder: (context, ref, child) { final count1 = ref.watch(counterProvider.select((value) => value.count1)); print("rebuilt counter 1 Text with val: $count1"); return Text(count1.toString()); }, ), Consumer( builder: (context, ref, child) { final count2 = ref.watch(counterProvider.select((value) => value.count2)); print("rebuilt Counter 2 Text with val: $count2"); return Text(count2.toString()); }, ), ElevatedButton( onPressed: () => ref.read(counterProvider.notifier).inc1(), child: const Text("Inc 1"), ), ElevatedButton( onPressed: () => ref.read(counterProvider.notifier).inc2(), child: const Text("Inc 2"), ), ], ), ); } }
2. 让状态类支持值相等判断
修改Counter类,重写==和hashCode:
class Counter { final int count1; final int count2; Counter(this.count1, this.count2); @override bool operator ==(Object other) => identical(this, other) || other is Counter && runtimeType == other.runtimeType && count1 == other.count1 && count2 == other.count2; @override int get hashCode => count1.hashCode ^ count2.hashCode; }
这样修改后:
- 点击
Inc 1时,只有第一个Consumer会重建并打印日志,第二个Consumer不会触发任何操作; - 状态更新时,Riverpod会先判断新旧
Counter的值是否相等,避免不必要的通知。
关于重复重建的说明
你看到的第一个Text重复打印两次,是因为原代码中TestWidget的build被触发了两次(可能是Flutter框架的正常重建逻辑,比如布局变化)。当你用Consumer包裹后,局部重建只会触发一次,重复打印的问题也会解决。
内容的提问来源于stack exchange,提问作者MohaAmiry

