Selector.shouldRebuild未调用且Widget始终重建,技术问题求助
版本信息
provider 6.0.5(当前最新版) Flutter 3.13.7 Dart 3.1.3
代码示例
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: ChangeNotifierProvider<MyState>( create: (context) => MyState(), child: const Body(), ), ), ); } } class MyState with ChangeNotifier { List<int> values = [0, 0, 0]; void increment(int key) { values[key]++; notifyListeners(); } } class Body extends StatelessWidget { const Body({super.key}); @override Widget build(BuildContext context) { var values = context.watch<MyState>().values; return Column( children: [ Row( children: [ for (int i = 0; i < values.length; i++) Selector<MyState, int>( selector: (_, state) { print("Selector index: $i, selector: ${state.values[i]}"); return state.values[i]; }, shouldRebuild: (previous, next) { print( "Should index: $i, prev: $previous, next: $next ${previous != next ? ", NEED REBUILD" : ""}"); return previous != next; }, builder: (_, __, ___) { print("Build index: $i"); return Expanded( child: Center( child: Text( "${values[i]}", textScaleFactor: 3, ))); }, ) ], ), Row( children: [ for (int i = 0; i < values.length; i++) Expanded( child: Center( child: TextButton( onPressed: () { print("-" * 20); print("Increment index: $i"); context.read<MyState>().increment(i); }, child: Text("Increment $i"), ), ), ) ], ), Center( child: Text( "Summ: ${values.reduce((value, element) => value + element)}", textScaleFactor: 2, ), ), ], ); } }
输出日志
Selector index: 0, selector: 0 Build index: 0 Selector index: 1, selector: 0 Build index: 1 Selector index: 2, selector: 0 Build index: 2 -------------------- Increment index: 1 Selector index: 0, selector: 0 Build index: 0 Selector index: 1, selector: 1 Build index: 1 Selector index: 2, selector: 0 Build index: 2
问题现象
build方法每次状态变化都会被调用shouldRebuild方法从未执行
问题原因
核心问题出在Body组件的build逻辑中:调用context.watch<MyState>()会让Body在MyState变化时完全重建。
当Body重建时,内部Row里通过for循环生成的Selector都是全新的组件实例。Provider的Selector内部会判断新旧组件实例是否相等(oldWidget != widget),如果不相等就直接失效缓存,跳过shouldRebuild检查,直接重新执行selector和builder方法,这就是shouldRebuild日志看不到、所有Selector都被强制build的原因。
修复方案
方案一:移除全局watch,避免Body整体重建
删掉Body中的context.watch<MyState>(),拆分组件或用Selector/Consumer按需获取数据,让Body不会因MyState变化而重建:
修改后的Body示例:
class Body extends StatelessWidget { const Body({super.key}); @override Widget build(BuildContext context) { return Column( children: [ Row( children: [ for (int i = 0; i < 3; i++) Selector<MyState, int>( selector: (_, state) { print("Selector index: $i, selector: ${state.values[i]}"); return state.values[i]; }, shouldRebuild: (previous, next) { print( "Should index: $i, prev: $previous, next: $next ${previous != next ? ", NEED REBUILD" : ""}"); return previous != next; }, builder: (_, value, ___) { print("Build index: $i"); return Expanded( child: Center( child: Text( "$value", textScaleFactor: 3, ))); }, ) ], ), Row( children: [ for (int i = 0; i < 3; i++) Expanded( child: Center( child: TextButton( onPressed: () { print("-" * 20); print("Increment index: $i"); context.read<MyState>().increment(i); }, child: Text("Increment $i"), ), ), ) ], ), Selector<MyState, int>( selector: (_, state) => state.values.reduce((a, b) => a + b), builder: (_, sum, ___) => Center( child: Text( "Summ: $sum", textScaleFactor: 2, ), ), ), ], ); } }
方案二:给Selector添加唯一Key
如果保留Body的watch逻辑,给每个Selector添加ValueKey(i),确保Body重建时,Provider能识别同一索引对应的Selector是同一个组件实例,这样oldWidget == widget成立,缓存不会失效,shouldRebuild会正常执行:
修改for循环内的Selector:
Selector<MyState, int>( key: ValueKey(i), // 添加唯一Key selector: (_, state) { print("Selector index: $i, selector: ${state.values[i]}"); return state.values[i]; }, // 其余代码不变 )
补充说明
修改provider源码中selector.dart里的shouldInvalidateCache判断逻辑,将oldWidget != widget ||改为oldWidget == null ||,会跳过组件实例变化的检查,强制使用缓存,此时shouldRebuild能正常执行,但这属于临时hack,不推荐作为正式解决方案。
内容的提问来源于stack exchange,提问作者ReinRaus

