【Flutter】Riverpod 的MVVM结构化,从另一种角度理解 RiverPod

3,244 阅读16分钟

从另一种角度理解 Riverpod

前言

说起 Riverpod 我们就不得不说起 Provider ,很多博主包括官方也推荐我们使用 Provider 进行状态的管理,为什么 Provider 好用我们还要使用 Riverpod 这个插件来替代它呢?

这就不得不说起 Provider 的痛点:

  1. Provider 是基于 InheritedWidget 封装,读取状态需要 BuildContxt,所以只能在 Widget 树中声明使用。而在有些场景不不一定能直接拿到 BuildContext,比如ViewModel (Controller) 中,如果通过传参等方式实现会增加代码的耦合度还有内存泄漏的风险,使用不当容易造成 ProviderNotFoundException 。

  2. 多个相同类型的Provider,需要自己维护一个 Key 进行区分,比如在同一个页面不同实例的页面中 MultiProvider 同一个状态类,你需要用key来维护和区分不同的状态类。搞的比较混乱

为什么说是痛点,也就是说是能通过其他的手段完成但是不爽利,又不是不能用!

但是现在有更好的方案 Riverpod ,它是在 Provider 的基础上进行重构,解决上述问题之余,提供了 更灵活/精细的状态管理机制,状态不可变,编译时类型安全、易于测试 等特性。还可以通过注解和代码生成快速的组成大规模的状态管理。

Riverpod 优点:全局定义ProviderScope了,不需要去考虑BuildContext的传递跟生命周期,

Riverpod 缺点:没有了Notify 主动通知,必须修改被观察的引用才能收到通知,并且 Widget 节点需要继承他自带的 ConsumerStatefulWidget 或者 ConsumerWidget 对整个结构改变较大。还有一个特殊的缺点是如果用了 @riverpod 这种注解生成代码,会有很多"难看"的生成文件,如果使用git工具多人协同开发可能导致冲突,是否需要添加过滤是一个问题。

接下来我来介绍一下如何使用 Riverpod 吧。

一、如何简单使用 Riverpod

导入 Riverpod 的插件

dependencies:
  flutter:
    sdk: flutter

  # Riverpod核心库
  flutter_riverpod: ^2.5.1

  #Riverpod注解 (可选)
  riverpod_annotation: ^2.3.5

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^4.0.0

  # Dart代码生成文件  (可选)
  build_runner: ^2.4.12

  # Riverpod代码生成器  (可选)
  riverpod_generator: ^2.4.3

  # 专为Riverpod设计的一套lint规则  (可选)
  riverpod_lint: ^2.3.13

除了 flutter_riverpod 的核心库,其他都是可选的,如果你想要代码注解自动生成可以选择,当然个人是推荐使用注解生成的。

1.1 简单的使用:

先在全局入口使用 ProviderScope 包裹MyApp

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const SettingPage(),
    );
  }
}

接下来定义简单的Provider,这里以老版本的 StateProvider 为例子:

final goodsAllProvider = StateProvider<int>((ref) => []);

我们就能直接使用了,但是要注意我们的 Widget 需要继承自 ConsumerStatefulWidget 或者 ConsumerWidget ,这里以 ConsumerStatefulWidget 为例子:


class ShopClassifyGoodsPage extends ConsumerStatefulWidget {
  @override
  ConsumerState<ConsumerStatefulWidget> createState() {
    return _ShopClassifyGoodsPageState();
  }
}
// State 也必须继承 ConsumerState
class _ShopClassifyGoodsPageState extends ConsumerState<ShopClassifyGoodsPage> {

@override
Widget build(BuildContext context) {
   //监听改变
  var count = ref.watch(goodsAllProvider);
  
  return GestureDetector(
          onTap: (){
              count++;
          },
          child: Container(child:Text("count"),));
  }
}

这是很简单的实例,但是我们的 State 状态如果不是一个基本类型呢?或者我想要一个去封装的状态类呢?

也很简单,我们定义一个状态类

class HomeState {

  final int count;

  HomeState(this.count);
}

使用一个 StateNotifier 来管理这些状态类 State

class HomeController extends StateNotifier<HomeState> {
  HomeController() : super(HomeState(0));

  void increment() {
    state = HomeState(state.count + 1);
  }
}

// 定义状态提供者
final homeProvider = StateNotifierProvider<HomeController, HomeState>((ref) => HomeController());

同时我们手动的定义 StateNotifierProvider 方便其他地方获取。

class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final HomeState state = ref.watch(homeProvider);
    final HomeController controller = ref.read(homeProvider.notifier);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("Flutter Demo Home Page"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              'Count: ${state.count}',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          print("当前的count:${state.count}");
          controller.increment();
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

这样做的好处是把页面与逻辑解耦,状态,逻辑,UI 分为三个文件进行分层,结构更清晰,也就是大家常说的 MVVM 架构。

1.2 关于多实例 family 的使用:
1.2.1 带状态的 Provider

默认我们的默认不带参数的实现:

class HomeController extends StateNotifier<HomeState> {
  HomeController() : super(HomeState(0));

  void increment() {
    state = HomeState(state.count + 1);
  }
}

// 定义状态提供者
final homeProvider = StateNotifierProvider<HomeController, HomeState>((ref) => HomeController());


默认的参数实现,如果在同一个引用周期内,怎么 watch 和 read 它都是一个引用。

而如果我们给 HomeController 加上 formType 属性的话。

final homeProvider = StateNotifierProvider.autoDispose.family<HomeController, HomeState, String>((ref, formType) {
  return HomeController(
    formType: formType,
  );
});

我们用 family 就可以区分,传入不同的 formType 就是不同的 Provider 对象。这一点对同页面不同的实例的场景很有用。

1.2.2 不带状态的 Provider

一句代码就成:

final userProvider = FutureProvider.autoDispose.family<String, String>((ref, id) {
  return ref
      .watch(userRepositoryProvider)
      .fetchUserInfo(id: id);
});

UserRepository 的代码,当然这里是用的注解生成的,不重要,后面会讲到。

class UserRepository {

  UserRepository({required this.dio});

  final Dio dio;


  Future<String> fetchUserInfo({required String id}) async {
    await Future.delayed(const Duration(seconds: 3));

    return Future.value("Newki");
  }
  
}

@riverpod
UserRepository userRepository(UserRepositoryRef ref) => UserRepository(dio: ref.read(dioProvider));

我们也能通过传递不同的参数实现不同的实例。这一点特别是在单例的 Provider 中很实用。

1.3 Provider 与 ref 的状态监听

之前我们理解了各种 Notifier 和 Provider 的定义,那么我们有具体有哪些 Provider 又该如何监听呢?

我们常用的 Provider 分为:

Provider:只存储 不可变 的值或对象,最简单的状态提供者,只对外提供访问状态值的接口,外部无法对状态值进行修改。

StateProvider:简单的状态更新

StateNotifierProvider:可以增加ViewModle,对具体观察者进行操作,不过么有主动通知的功能,只能通过从新设置应用,做到主动更新

FutureProvider:处理 异步操作,如:从网络请求数据数据,它会再Future完成时通知其观察者。通常与 autoDispose 修饰符一起使用。

StreamProvider:处理 基于流的异步数据,监听一个Stream,并在新数据到达前通知其观察者。

以及 NotifierProvider(新引入) 和 AsyncNotifierProvider (新引入)

NotifierProvider 用于管理同步的应用状态。它基于 Notifier 类,你可以在 Notifier 类中定义状态和操作逻辑。与 StateNotifierProvider 相比,NotifierProvider 提供了一种更简洁、更现代化的管理状态的方式。

AsyncNotifierProvider 用于管理异步状态。它基于 AsyncNotifier 类,你可以在 AsyncNotifier 类中定义异步操作和状态管理逻辑。它主要用来处理异步任务,如网络请求、数据库查询等。

例如之前的 HomeController 我们使用的 StateProvider ,这里我们就完全可以替换为 NotifierProvider,更加的简洁:

class SettingViewmodel extends Notifier<SettingState> {

  void fetchUser() async {

    Future.delayed(const Duration(seconds: 2));
    state = SettingState()
      ..count = state.count + 1
      ..userName = "Newki";
  }

  // 重写 build 用于返回初始的状态
  @override
  SettingState build() {
    return SettingState();
  }
}

final settingProvider = NotifierProvider<SettingViewmodel, SettingState>(
      () => SettingViewmodel(),
);

在观察使用的时候,我们通过 ref 对象可以实现很多操作:

watch() :监听Provider,当状态改变时,使用 watch() 的 Widget 会自动重建。

read() :只读取Provider的当前状态,状态改变,Widget不会重建。

listen() :通常用于在 build() 中监听Provider,当状态改变时,会调用设置的监听器,监听器会在idget重建时自动移除。

listenManual() :通常在 State.initState() 或其它生命周期中监听Provider,此方法返回一个 ProviderSubscription 对象,可以使用它来停止监听close(),或者读取Provider的当前状态。

refresh() :立即使Provider的当前状态无效,重新计算并返回新值,常用于触发异步Proivder的重新获取数据,如:下拉刷新、错误重试 等场景。

invalidate() :使Provider的当前状态无效,然后在下一次读取provider或者下一帧时,Provider会被重新计算。refresh() 是同步的,它是 异步 的,没有返回值。

exists() :判断 Provider 是否已经初始化。

我们最常用的也就是前三种。

这里的难点是怎么理解 watch 和 read 呢?如果按 MVVM 分层,我是获取的是 ViewModel 还是 State 呢?

其实很简单理解,主要是看要观察的 provider 是否带状态 (Notifier)

比如我们上面定义的 class SettingViewmodel extends Notifier<SettingState> 我们在页面中操作的时候:

    //观察全部的状态
    final state = ref.watch(settingProvider);

    //观察状态内的某一个属性
    final countState = ref.watch(settingProvider.select((value)=>value.count));

    // 获取控制类
    final viewModel = ref.read(settingProvider.notifier);

watch 就是获取可变的状态,read 就是获取不可变的控制类,如果我们想要观察 State 类中的某一个属性的变化,我们也能通过 xxProvider.select 的方式进行操作。

当我们观察的就是普通的 Provider 不带状态(Notifier)我们直接 watch 即可观察它执行的结果。

例如:

@riverpod
Future<String> user(
    UserRef ref, {
      required String id,
    }) {
  return ref
      .read(userRepositoryProvider)
      .fetchUserInfo(id: id);

}

使用:

  @override
  Widget build(BuildContext context, WidgetRef ref) {

    final userFuture = ref.watch(userProvider(id: "123"));

    return Scaffold(
        appBar: AppBar(title: const Text('User Test')),
        body:
      
        userFuture.when(
          data: (userInfo) => Center(child: Text('User info: $userInfo')),
          loading: () => const Center(child: CircularProgressIndicator()),
          error: (err, stack) => Center(child: Text('Error: $err')),
        ),

    );
  }

其效果就是进入页面先转2秒的圈,然后显示对应的文本。

二、结合注解如何使用 Riverpod

前面的一些代码会贴出了一些 @riverpod 的注解,这是通过注解生成的代码。需要额外导入 riverpod_annotation riverpod_generator build_runner 等插件,在前面的文章已经给出。

既然 Riverpod 我们可以自己手写,为什么还需要注解生成呢?不用注解生成行不行?

可以自己手写,但是比较麻烦,使用注解更加方便也会自动优化代码。

例如我们写一个默认的 Provider

@riverpod
UserRepository userRepository(UserRepositoryRef ref) => UserRepository(dio: ref.read(dioProvider));

他会帮我们默认实现

image.png

包括带状态(Notifier)的也会自动生成 AutoDispose 类型。

它会在我们没有引用之后自动回收,如果我们就是想让一个 Provider 全局存在怎么办?

@Riverpod(keepAlive: true)
UserRepository userRepository(UserRepositoryRef ref) => UserRepository(dio: ref.read(dioProvider));

我们可以手动的指定 keepAlive 为 true

此时生成的代码就变了:

image.png

有些同学可能就说了,那这简单的代码我自己手写也行啊,我能行。

相信我随着你的项目越来越复杂,你真不行。特别是带有参数的 Provider 各种 family 的写法,真的痛苦,特别是各种等级嵌套依赖:

class SettingRepository {
  SettingRepository({required this.dio, required this.api});

  final Dio dio;
  final String api;

  Future<List<String>> fetchSettingConfig({required int type}) async {
    await Future.delayed(const Duration(seconds: 3));

    return Future.value(["1", "2", "3"]);
  }
}

@riverpod
SettingRepository settingRepository(SettingRepositoryRef ref, {required String apiUrl}) => SettingRepository(
      dio: ref.watch(dioProvider),
      api: apiUrl,
    );

类似这种生成的代码都有 200 多行了,说实话还是注解生成更省心,也更放心。

用法:

这里演示带状态和(Notifier)不带状态的两种:

import 'package:hello_demo2/user/user_model.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'user_view_model.g.dart';

@riverpod
class UserViewModel extends _$UserViewModel {
  @override
  UserModel? build() => null;

  Future<String> fetchUser(String userId) async {
    await Future.delayed(const Duration(seconds: 2));

    return Future.value("zhang san");
  }

}

@riverpod
Future<String> user(
    UserRef ref, {
      required String id,
    }) {
  return ref
      .read(userViewModelProvider.notifier)
      .fetchUser(id);
}


其实可以简单理解为 ViewModel (肯定带状态)的和普通对象的

我们执行命令:

dart run build_runner build
或者
dart run build_runner watch -d

一个是生成一个是观察生成,都可以使用 -d 选项可以帮助你清理这些冲突文件,确保生成的代码可以顺利写入。

两个都可以用。并且报错提示页很友好,如果出错了直接告诉你哪个类哪一行代码有问题。

使用 watch 的话,保存该文件后,build_runner 就会开始工作,并在同一文件夹中生成 xx_provider.g.dart:

生成的文件就包含了相对于的 xxprovider的类型。

如果你想保持单例:

@Riverpod(keepAlive: true)
UserRepository userRepository(UserRepositoryRef ref) => UserRepository(dio: ref.read(dioProvider));

这样就算没有地方引用了,该 Provider 也不会回收,一直"活着" 除非你手动的回收。使用 dispose() 来手动回收 Provider。

其实我们也可以手动的控制 provider 的声明周期,例如:

@riverpod
Future<String> user(UserRef ref, {required int id}) {
  // 调用 ref.keepAlive() 方法获取一个 KeepAliveLink 实例,这个实例用于延长 provider 的生命周期。
  final link = ref.keepAlive();
  // 启动一个 60 秒的定时器。当定时器到期时,调用 link.close() 方法关闭 KeepAliveLink,从而触发 provider 的清理和释放资源
  final timer = Timer(const Duration(seconds: 60), () {
    // 如果 60 秒内没有其他地方使用这个 provider,它将会被自动清理
    link.close();
  });
  // 注册一个清理回调,当 provider 被清理时取消定时器,确保不会有悬挂的定时器在后台运行
  ref.onDispose(() => timer.cancel());

  return ref
      .watch(userRepositoryProvider)
      .fetchUser(id: id);
}

这段代码定义了一个具有自动清理功能的 FutureProvider。该 provider 在被首次使用后,会启动一个 60 秒的定时器,如果在这个时间间隔内没有其他地方使用该 provider,它将会自动关闭并释放资源。这种机制有助于优化应用的资源管理,确保不必要的状态不会长时间占用内存。以确保 provider 的状态在长时间未被使用时自动清理,从而提高应用的性能和资源利用率。

越说越复杂了,虽然相比 Riverpod 使用注解确实又多了一些学习成本,但是也有一些简单的小技巧。

我们可以使用

image.png

插件内置的一些 live template 来完成快速生成模板代码。

当然你也可以根据自己的风格手动的定义一些 live template ,例如:

import 'package:hello_demo2/$name_lowercase$/$name_lowercase$_model.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part '$name_lowercase$_view_model.g.dart';

@riverpod
class $name$ViewModel extends _$ $name$ViewModel {

  @override
  $name$Model? build() => null;

}

image.png

或者你也可以自己实现一个自己的带状态(Notifier)和不带状态的Provider模板,方便注解的生成。

虽然注解的功能很多其实我们能用到也就是一些常用的功能而已,还是很好入手的。

三、从MVVM与依赖注入的角度理解Riverpod

我们前文一直强调带状态(Notifier)和不带状态的区别。是不同的用法。

在实际开发的过程中,我们肯定会进行一定的结构化,不可能全部的代码都在一个 Widget 中,比较好也是大家比较推崇的做法是 MVVM 架构。

我们把 Widget + ViewModel + State 组成一个完整的结构,当然你可以叫任意的名字,但是思路是不变的,而 MVVM 这种架构中,ViewModel 就是带状态的(Notifier)的,它的用法和其他的默认的方法是不同的,我们也是反复的提到过。

class SettingViewmodel extends Notifier<SettingState> {

  void fetchUser() async {

    Future.delayed(const Duration(seconds: 2));
    state = SettingState()
      ..count = state.count + 1
      ..userName = "Newki";
  }

  // 重写 build 用于返回初始的状态
  @override
  SettingState build() {
    return SettingState();
  }
}

final settingProvider = NotifierProvider<SettingViewmodel, SettingState>(
      () => SettingViewmodel(),
);

或者使用注解:

@riverpod
class UserViewModel extends _$UserViewModel {
  @override
  UserModel? build() => null;

  Future<void> addUser(String id) async {
    
    // 将本地缓存标记为脏
    ref.invalidateSelf();
    
  }

  Future<String> fetchUser(String userId) async {
    await Future.delayed(const Duration(seconds: 2));

    return Future.value("zhang san");
  }

}

这些都好理解,其中我们在 Provider 的时候为什么还能直接用 ref 对象去引入其他的 Provider 呢?

其实我们把 Riverpod 当做一个依赖注入框架来理解就很好入手,类似 Fultter 的 Getit 或者 Androd 的 Hilt 这样的依赖注入框架。

感觉更像 Android 的 Hilt 只要你加上注解,你就生成了对应的 Provider ,那我就可以在一个 Provider 中直接找到我想要的任意 Provider 对象。

举例说明:

//不带参数默认的 Provider
@riverpod
Future<String> user(
    UserRef ref, {
      required String id,
    }) {
  return ref
      .read(userRepositoryProvider)
      .fetchUserInfo(id: id);

}


//带参数的 family 的 Provider
@riverpod
Future<List<String>> config(
    ConfigRef ref, {
      required int id,
    }) {
  return ref
      .read(SettingRepositoryProvider(apiUrl: "http://www.baidu.com/setting"))
      .fetchSettingConfig(type: id);

}

两个默认的普通不带 Notifier 的 Provider 的定义,其中用到了 userRepositoryProvider(不带参数) 和 SettingRepositoryProvider(带参数)的两个 Provider。

其实它们的依赖逻辑是一样的,只是带参数的用构造的方式需要传入参数,不带参数用函数的快速方式。

SettingRepository:

class SettingRepository {
  SettingRepository({required this.dio, required this.api});

  final Dio dio;
  final String api;

  Future<List<String>> fetchSettingConfig({required int type}) async {
    await Future.delayed(const Duration(seconds: 3));

    return Future.value(["1", "2", "3"]);
  }
}

@riverpod
SettingRepository settingRepository(SettingRepositoryRef ref, {required String apiUrl}) => SettingRepository(
      dio: ref.watch(dioProvider),
      api: apiUrl,
    );

和 UserRepository :

class UserRepository {

  UserRepository({required this.dio});

  final Dio dio;

  Future<String> fetchUserInfo({required String id}) async {
    await Future.delayed(const Duration(seconds: 3));

    return Future.value("Newki");
  }

}

@Riverpod(keepAlive: true)
UserRepository userRepository(UserRepositoryRef ref) => UserRepository(dio: ref.read(dioProvider));

其实也可以看到他们是类似的,只是 UserRepository 不带参数是默认的 Provider,而 SettingRepository 带参数,所以他是 Provider - family 类型的,它是多实例的。

而这些 Repository 在构造的时候直接都用到了 dioProvider 对象,这又是一个新的无参数默认的 Provider 。

@Riverpod(keepAlive: true)
Dio dio(DioRef ref) {
  return Dio();
}

我们定义为全局不回收的类型而已,那么当我们使用 userProvider 或者 configProvider 的时候就会到依赖注入池中找到 UserRepository 或 SettingRepository对象,而它们的创建又需要 dioProvider 这个对象,从而一层一层的注入对象。

这样是不是很好理解了。

四、状态对象的赋值与刷新

不是只需要修改状态中的某个属性就能自动刷新了吗?其实是 Getx 的魔法。在 Riverpod 中我们需要重新创建一个状态对象才能其中的差异进行触发局部刷新。

Flutter 的状态管理(包括 Riverpod)利用不可变对象的特性来高效地管理和更新 UI,如果有从 Getx 框架转过来的同学可能很不习惯,你需要了解的是不可变性。

不可变性是指一个对象一旦被创建后,其状态就不能再被修改。状态管理中的不可变性具有以下几个优点:

不可变对象的状态一旦设置就不会改变,因此可以确保状态在整个生命周期中保持一致,减少了调试的复杂性。

当对象是不可变的,你可以更轻松地跟踪状态的变化,避免意外的状态修改。

在多线程环境中,不可变对象天生是线程安全的,避免了并发修改带来的问题。

虽然重新创建对象似乎增加了开销,但实际上,现代 Dart 和 Flutter 的优化机制使得这种开销非常小,往往能够带来更多的性能优化。

当状态改变时,Flutter 的 build 方法会重新运行。如果状态对象是不可变的,Flutter 可以更轻松地判断是否需要重绘界面。

Dart 的垃圾回收机制(GC)对短期存在的对象(通常是不可变对象)进行了优化,使其能够快速被回收。

既然我们知道了需要重新创建对象才能触发刷新,那么有哪些方法呢?

你可以直接 new 一个对象直接赋值各种属性,也可以使用 freezed 的插件来生成,反正我们都已经用了 build_runner 了,配合 freezed 完成状态岂不美哉,可以参考我之前的文章【自动化生成代码,那些配合build_runner使用的插件】

当然我们也可以使用编辑器插件来实现,例如 Dart Data Class,具体用法可以参考我之前的文章【不想用build_runner自动生成代码?用这些插件工具更快速的实现吧】

image.png

一秒钟就自动生成了对应的 copywith 代码:

class SettingState{

  String? userName;

  int count = 0;

  String? linkUrl;

  int type = 0;
  
  SettingState({
    this.userName,
    required this.count,
    this.linkUrl,
    required this.type,
  });


  SettingState copyWith({
    String? userName,
    int? count,
    String? linkUrl,
    int? type,
  }) {
    return SettingState(
      userName: userName ?? this.userName,
      count: count ?? this.count,
      linkUrl: linkUrl ?? this.linkUrl,
      type: type ?? this.type,
    );
  }
  

}

其实除了 Getx 的状态刷新方式之外其他状态管理插件基本都需要用到 copywith 的这种方式来实现刷新,习惯就好。因为担心 Getx 的同学转过来不熟悉所以特别开一段介绍一下。

总结

本文我们学习(回顾)了 Riverpod 的使用,以及推荐的结合注解的定义,以及 ViewModel 与 普通对象依赖注入的各种方式。

讲到这里,我们再回过头看看 Provider 的痛点,是不是更加能理解 Riverpod 的灵活性,相对于 Provider插件被限制在 Widget 树上面, 我们通过 Riverpod 插件就能实现各种想要的"骚操作"。

我们常说状态管理框架 Riverpod 很好用,但是很难理解,我们从依赖注入角度理解其实 Riverpod 是一个状态管理 + 依赖注入的框架,理解了依赖注入的思想,再来看状态管理就更容易入手。

  1. 依赖注入

Riverpod 允许你在不同的组件之间共享依赖项。通过定义 Provider,你可以将依赖项注入到需要的地方,而无需手动传递。这些依赖项可以是任何类型的对象,例如网络客户端、数据库实例、配置数据等。

  1. 状态管理

在 Riverpod 2.0 及以上版本中,官方推荐的状态管理方式有了一些变化。主要推荐以下几种 Provider:

  • Provider
  • FutureProvider
  • StreamProvider
  • NotifierProvider(新引入)
  • AsyncNotifierProvider(新引入)

而传统的 StateNotifier 和 StateNotifierProvider 在一定程度上不再被推荐,并且新的 NotifierProvider 和 AsyncNotifierProvider 提供了更简洁和强大的功能。

相比而言我们最常用的只有 Notifier 和 NotifierProvider (带状态),Provider,FutureProvider 了,当然如果你是用 @rivepod 生成的话,是会自动区分 AutoDispose 类型的。

总体而言,Riverpod 的强大之处在于它将状态管理和依赖注入紧密结合。你可以在 Provider 中注入依赖项,然后在需要的地方通过 ref.read 或 ref.watch 来获取这些依赖项,也可以实现状态的监听与改变,从而实现模块化和高可维护性的代码。

那本文的代码比较简单,相对比较基础,并且全部的代码也已经在文中展出,后期看情况会出 配合 Hook 和 组件化的完整 Demo 的。

今天的分享就到这里啦,当然如果我的理解错误也希望大家能评论区交流一起学习进步。如果我的文章有错别字,不通顺的,或者代码、注释、有错漏的地方,同学们都可以指出修正。

如果感觉本文对你有一点的启发和帮助,还望你能点赞支持一下,你的支持对我真的很重要。

Ok,这一期就此完结。