为何父StatelessWidget重建时Provider的create()方法不重新执行?
Provider未随父组件重建更新的原因解析
我知道处理这类状态管理场景常用ChangeNotifierProvider(CNP)等方案,但想搞懂Provider的实际工作机制。
当包含Provider的StatelessWidget因输入值变化触发重建(build方法再次执行)时,我原本预期会创建一个新的Provider实例,携带更新后的数值,进而触发所有使用Provider.of<T>(context, listen:true)的下游组件更新——但实际并非如此:父StatelessWidget带着新输入值重建时,Provider并没有重建自身,这是为什么?
我并非认为这是合理的实现方式,只是想理解背后的逻辑。我试过多种重构方式(包括用StatefulWidget做父组件),也知道正确的做法是用可修改状态的对象作为Provider(比如包含数值和修改方法的类,无需重建父组件,直接调用方法修改数值即可),但就是想搞清楚当前实现不生效的原因——它表现得就像Provider是const定义的,但传入的明明不是const值。
演示代码
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter/scheduler.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: CounterUpper(), ), ), ); } } class _CounterUpperState extends State<CounterUpper> with TickerProviderStateMixin { Ticker? ticker; double animationValue = 0; @override void initState() { print('Running _CenterUpperState.initState()'); super.initState(); ticker = Ticker((Duration elapsed) { if (elapsed.inSeconds - animationValue > 1) { setState(() { print( 'Running _CenterUpperState.setState() with animationValue: $animationValue and elapsed.inSeconds: ${elapsed.inSeconds}'); animationValue = elapsed.inSeconds.toDouble(); }); } if (elapsed.inSeconds > 5) { ticker?.stop(); } }); ticker!.start(); } @override Widget build(BuildContext context) { print( 'Running _CenterUpperState.build() with animationValue: $animationValue'); return ProviderHolder(animationValue: animationValue); } } class ProviderHolder extends StatelessWidget { const ProviderHolder({super.key, required this.animationValue}); final double animationValue; @override Widget build(BuildContext context) { print( 'Running ProviderHolder.build() with animationValue: $animationValue'); return Provider<ValueWrapper>( create: (_) { print('Running Provider.create() with animationValue: $animationValue'); return ValueWrapper(animationValue); }, child: ContentHolder(), ); } } class ContentHolder extends StatelessWidget { const ContentHolder({super.key}); @override Widget build(BuildContext context) { final double providerValue = Provider.of<ValueWrapper>(context, listen: true).value; print('Running ContentHolder.build() with providerValue: $providerValue'); return Text(providerValue.toString()); } } class ValueWrapper { const ValueWrapper(this.value); final double value; } class CounterUpper extends StatefulWidget { const CounterUpper({super.key}); @override _CounterUpperState createState() => _CounterUpperState(); }
原因解析
核心逻辑在于Provider默认会复用已有的实例,除非配置了update参数,或者组件的key发生变化:
- Provider是InheritedWidget的封装,Flutter在组件树diff时,会对比新旧Provider的
key和配置逻辑。如果key相同且未配置update,它会保留之前创建的实例,不会重新调用create方法。 - 在你的代码中,每次
ProviderHolder重建时,都会生成一个新的Provider对象,但因为没有给Provider设置唯一标识的key,Flutter的diff算法判定这是同一个组件,因此复用了旧实例,导致create方法不会重新执行,下游组件自然拿不到新值。 - 另外,你的
ValueWrapper是不可变类(字段为final),即便Provider想更新状态,也无法修改已有实例的数值,只能通过创建新实例实现,但Provider的默认逻辑不会主动这么做。
验证方案
给Provider添加与animationValue绑定的key,这样每次数值变化时,Provider的key不同,Flutter会销毁旧实例并创建新的,create方法会执行,下游组件也能拿到新值:
return Provider<ValueWrapper>( key: ValueKey(animationValue), // 添加唯一key create: (_) { print('Running Provider.create() with animationValue: $animationValue'); return ValueWrapper(animationValue); }, child: ContentHolder(), );
内容的提问来源于stack exchange,提问作者Brian Thompson
相关产品推荐
相关产品推荐

