Riverpod中ref.watch触发Notifier重建而ref.read不触发的原因及最佳实践
Flutter Riverpod中ref.watch与ref.read的行为差异及实践方案
问题场景代码
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get/get.dart'; // Notifier class class DumTabNotifier extends Notifier<DumTabState> { final _edtLmp = TextEditingController(); @override DumTabState build() { ref.onDispose(() { _edtLmp.clear(); }); return DumTabState( gestationalAgeString: '', edd: '', eofTrimester: '', bofTrimester: '', gaWeeks: 0, gaDays: 0, edtLmp: _edtLmp, ); } void calculateFromLmp() { DateTime? lmpDT = extractDateFromStringBasedOnLocale(_edtLmp.text); if (lmpDT == null) return; final todayGlobal = ref.watch(todayGlobalProvider); // or ref.read(todayGlobalProvider) // Calculation logic... } } final dumTabProvider = NotifierProvider<DumTabNotifier, DumTabState>(() { return DumTabNotifier(); });
问题解答
1. 为什么ref.watch会触发Notifier销毁重建,ref.read不会?
Riverpod的核心是依赖追踪机制:
ref.watch会在调用的DumTabNotifier与todayGlobalProvider之间建立持久依赖关系。当todayGlobalProvider状态变化时,Riverpod会判定依赖它的DumTabNotifier需要重新初始化——因为Notifier的build方法是状态的唯一入口,非build方法中使用ref.watch会让依赖关联到Notifier实例本身,依赖更新时Notifier会被销毁重建,类成员_edtLmp会被重新创建,再加上onDispose中的clear操作,最终导致控制器内容清空。ref.read只是一次性读取todayGlobalProvider的当前值,不会建立持久依赖,因此todayGlobalProvider状态变化时,不会触发DumTabNotifier的任何重建操作。
2. 此场景下的最佳实践
- 临时取值用ref.read:如果
calculateFromLmp仅在调用时需要todayGlobal的当前值,不需要随todayGlobalProvider变化自动重新计算,直接用ref.read即可,避免不必要的重建。 - 需响应变化时,将watch放在build方法中:如果需要在
todayGlobalProvider变化时自动重新执行计算逻辑,应把ref.watch移到build方法内,通过状态关联或监听触发计算:@override DumTabState build() { final todayGlobal = ref.watch(todayGlobalProvider); // 监听todayGlobal变化,自动触发计算 ref.listen(todayGlobalProvider, (previous, next) { calculateFromLmp(); }); ref.onDispose(() { _edtLmp.clear(); }); return DumTabState(/* ... */); }
3. 跨页面访问TextEditingController的替代方案
Riverpod不建议在业务Notifier中存储TextEditingController,因为它是与Widget生命周期强绑定的框架对象,易引发生命周期不一致问题。推荐两种方案:
方案1:用独立Provider管理控制器生命周期
创建专门的Provider持有TextEditingController,由Riverpod统一管理其生命周期,跨页面通过ref.watch获取:
final lmpTextEditingControllerProvider = Provider<TextEditingController>((ref) { final controller = TextEditingController(); ref.onDispose(() => controller.dispose()); // 确保不再使用时销毁 return controller; });
页面中使用:
TextField(controller: ref.watch(lmpTextEditingControllerProvider))
方案2:解耦控制器与业务逻辑,存储文本状态而非控制器
将TextField的文本内容作为业务状态存在DumTabNotifier中,页面通过onChanged同步文本到状态,其他页面监听状态获取内容:
// 修改DumTabState class DumTabState { final String lmpText; // 其他字段... DumTabState({required this.lmpText, /* ... */}); } // 修改Notifier class DumTabNotifier extends Notifier<DumTabState> { @override DumTabState build() { return DumTabState(lmpText: '', /* ... */); } void updateLmpText(String text) { state = state.copyWith(lmpText: text); } void calculateFromLmp() { DateTime? lmpDT = extractDateFromStringBasedOnLocale(state.lmpText); // 计算逻辑... } }
页面中使用:
TextField( onChanged: ref.read(dumTabProvider.notifier).updateLmpText, controller: TextEditingController(text: ref.watch(dumTabProvider).lmpText), )
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

