You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 00:27:04