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

从Provider迁移至RiverPod Generator:状态管理方案抉择咨询

从Provider(ChangeNotifier)迁移到RiverPod的方案选择

问题背景

我正从旧的Provider及其ChangeNotifier迁移至RiverPod与RiverPod Generator,目前正在熟悉新语法以提升使用信心。习惯用复杂类表示状态,比如之前用如下类管理已登录用户状态:

class UserLoggedProvider extends ChangeNotifier {
   User? user;
   Map<String, dynamic>? userConfig;
   ...

   setLoginData(LoginResponse response) {
      this.user = response.user;
      this.userConfig = response.configs;
      notifyListeners();
   }
   ...
}

尝试用两种方案复刻该逻辑,均可行,但纠结选择:

方案1

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'test.g.dart';

class TestProvider {
  final TestProviderRef _ref;

  TestProvider(this._ref);

  String? _testString;

  String? get testString => _testString;

  void setTestString(String value) {
    _testString = value;
    _ref.notifyListeners();
  }
}

@riverpod
TestProvider testProvider(TestProviderRef ref) => TestProvider(ref);

方案2

import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'test2.freezed.dart';
part 'test2.g.dart';

@freezed
class Test2Data with _$Test2Data {
  const factory Test2Data({
    String? testString,
  }) = _Test2Data;

  factory Test2Data.fromJson(Map<String, dynamic> json) =>
      _$Test2DataFromJson(json);
}

@riverpod
class Test2Provider extends _$Test2Provider {
  @override
  Test2Data build() => const Test2Data();

  void setTestString(String value) {
    state = state.copyWith(testString: value);
  }
}

方案2用不可变状态更规范,但样板代码多,更新代码也更繁琐:

ElevatedButton(onPressed: () {
   testRef.setTestString("Random Value: ${Random.secure().nextDouble()}");
   ref.watch(test2ProviderProvider.notifier).setTestString("Random Value: ${Random.secure().nextDouble()}");
}, child: Text("Set value"))

提问:未来应采用哪种方案?是否有更优方案?


方案分析与推荐

核心差异对比

  • 方案1:复刻ChangeNotifier的可变状态逻辑,状态存在Provider类的私有变量中,手动调用_ref.notifyListeners()触发更新。优点是上手快、代码简洁,和旧写法习惯一致;缺点是状态可变易引发无意识修改,状态变更难以追踪,调试成本高,长期维护容易出问题。
  • 方案2:遵循RiverPod官方推荐的不可变状态+自动状态管理模式,用Freezed生成不可变数据类,通过state = state.copyWith(...)更新状态。优点是状态不可变保证数据一致性,RiverPod自动感知状态变化并通知监听者,调试时可清晰追踪状态变更历史,符合社区最佳实践;缺点是需要依赖代码生成工具,初期看起来样板代码多,但大部分代码是自动生成的,手写逻辑并不复杂。

优先选择方案2

从项目长期维护、扩展性和稳定性考虑,方案2是更合适的选择:

  1. 状态安全:不可变状态避免了意外修改,所有状态变更都通过可控的copyWith操作完成,逻辑清晰,减少潜在bug。
  2. 调试便捷:每次状态更新都是生成新对象,配合RiverPod DevTools可以轻松回溯状态变化的全过程。
  3. 生态支持:官方文档和社区案例普遍采用这种模式,遇到问题更容易找到解决方案,团队协作时代码风格更统一。
  4. 扩展性强:当状态复杂度提升(比如增加更多字段、集成异步逻辑),方案2的架构能轻松适配,而方案1的可变状态模式会逐渐陷入维护困境。

针对你觉得更新代码繁琐的问题,可以通过以下方式简化:

  • 复用notifier实例:
final test2Notifier = ref.watch(test2ProviderProvider.notifier);
ElevatedButton(
  onPressed: () => test2Notifier.setTestString("Random Value: ${Random.secure().nextDouble()}"),
  child: const Text("Set value"),
)
  • 使用ref.read替代ref.watch(Widget内仅调用方法时无需监听notifier变化):
ElevatedButton(
  onPressed: () => ref.read(test2ProviderProvider.notifier).setTestString("Random Value: ${Random.secure().nextDouble()}"),
  child: const Text("Set value"),
)

更优方案的可能性

方案2已经是RiverPod的标准最佳实践,没有通用的更优方案,但可根据场景微调:

  • 简单状态场景:如果状态只是单个基础类型(如字符串、数字),可以直接用StateProvider,无需自定义notifier:
@riverpod
StateProvider<String?> simpleString(SimpleStringRef ref) => StateProvider((ref) => null);

更新时直接执行ref.read(simpleStringProvider.notifier).state = "new value",代码更简洁。

  • 异步逻辑场景:结合AsyncValue和FutureProvider/StreamProvider,或在自定义notifier中处理异步操作,方案2的架构可无缝兼容这类需求。

内容的提问来源于stack exchange,提问作者Ottavio Miele

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:52:50