当前位置: 首页 > wzjs >正文

杭州做网站公司百家港 seo服务

杭州做网站公司,百家港 seo服务,商学院网站建设建议,做中澳原产地证的网站三年前我们通过 《Flutter Riverpod 全面深入解析》 深入理解了 riverpod 的内部实现,而时隔三年之后,如今Riverpod 的主流模式已经是注解,那今天就让我们来聊聊 riverpod 的注解有什么特殊之处。 前言 在此之前,我们需要先回忆…

三年前我们通过 《Flutter Riverpod 全面深入解析》 深入理解了 riverpod 的内部实现,而时隔三年之后,如今Riverpod 的主流模式已经是注解,那今天就让我们来聊聊 riverpod 的注解有什么特殊之处。

前言

在此之前,我们需要先回忆一下,riverpod 最明显的特点是将 BuildContext 转换成 WidgetRef 抽象 ,从而让状态管理不直接依赖 BuildContext ,所以对应的 Provider 可以按需写成全局对象,而在 riverpod 里,主要的核心对象有:

  • ProviderScopeInheritedWidget 实现,共享实例的顶层存在,提供一个 ProviderContainer 全局共享
  • ProviderContainer:用于管理和保存各种 “Provider” 的 State ,并且支持 override 一些特殊 “Provider” 的行为,还有常见的 read\watch\refesh
  • Ref :提供 riverpod 内的 “Provider” 交互接口,是 riverpod 内 ProviderElementBase 的抽象
  • ProviderElementBase : Ref 的实现,每个 “Provider” 都会有自己的 “Element” ,而构建 “Provider” 时是传入的 Create 函数会在 “Element” 内通过 “setState” 调用执行,比如 StateProvider((ref)=> 0) 这里的 ref ,就是内部在 ”Element“ 里通过 setState(_provider.create(this)); " 的时候传入的 this
  • WidgetRef :替代 Flutter BuildContext 的抽象,内部通过继承 StatefulWidget 实现,作为 BuildContext 的对外替代

所以 “Provider” 里的 Ref 和 “Consumer” 的 WidgetRef 严格来说是两个不同的东西,只是它们内部都可以获取到 ProviderContainer ,从而支持对应 read\watch\refesh 等功能,这也是为什么你在外部直接通过 ProviderContainer 也可以全局直接访问到 read\watch\refesh 的原因。

另外,riverpod 内部定义了自己的 「Element」 和 「setState」实现,它们并不是 Flutter 里的 Element 和 setState,所以上面都加了 “”,甚至 riverpod 里的 “Provider” 和 Provider 状态管理库也没有关系, 这么设计是为了贴合 Flutter 本身的 「Element」 和 「setState」概念,所以这也是为什么说 riverpod 是专为 Flutter 而存在的设计。

注解模式

现在 riverpod 更多提倡使用注解模式,注解模式可以让 riverpod 使用起来更方便且规范,从一定程度也降低了使用难度,但是也对初学者屏蔽了不少过去的手写实现,导致在出现问题时新手也可能会相对更蒙。

简单函数注解

首先我们看这个简单的代码,我们在 main.dart 里添加了了一个 @riverpodhelloWorld ,然后运行 flutter pub run build_runner build --delete-conflicting-outputs ,可以看到此时生成了对应的 main.g.dart 文件:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';part 'main.g.dart';
String helloWorld(Ref ref) {return 'Hello world';
}
class MyApp extends ConsumerWidget {Widget build(BuildContext context, WidgetRef ref) {final String value = ref.watch(helloWorldProvider);return MaterialApp(home: Scaffold(appBar: AppBar(title: const Text('Example')),body: Center(child: Text(value),),),);}
}

我们看 main.g.dart 文件,可以看到,根据 @riverpod 的规则, helloWorld 会生成一个 helloWorldProvider 实例让我们在使用时 read/watch/refresh :

// GENERATED CODE - DO NOT MODIFY BY HANDpart of 'main.dart';// **************************************************************************
// RiverpodGenerator
// **************************************************************************String _$helloWorldHash() => r'9abaa5ab530c55186861f2debdaa218aceacb7eb';/// See also [helloWorld].
(helloWorld)
final helloWorldProvider = AutoDisposeProvider<String>.internal(helloWorld,name: r'helloWorldProvider',debugGetCreateSourceHash:const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash,dependencies: null,allTransitiveDependencies: null,
);('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef HelloWorldRef = AutoDisposeProviderRef<String>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

通过生成的代码,我们可以看到:

  • _$helloWorldHash() :它主要是用于提供一个唯一标识,用于追踪 Provider 的来源和状态,它是被 debugGetCreateSourceHash 所使用,例如在 Debug 模式下 hotload 时,riverpod 会用这个值来判断当前 provider 是否需要重建,比如当你重新生成的时候 hash 值就会出现变化。
  • helloWorldProviderAutoDisposeProvider 的实例,也就是默认情况下 @riverpod 生成的都是自动销毁的 Provider ,

这里默认使用 AutoDisposeProvider ,也是为了更好的释放内存和避免不必需要的内存泄漏等场景, AutoDisposeProvider 内部,在每次 readinvalidate 、页面退出、ProviderContainer 销毁等场景会自动调用 dispose 。

异步函数注解

接着,如果给 helloWorld 增加 async ,那么我们得到一个 AutoDisposeFutureProvider ,同理,如果是 async* 就会生成一个 AutoDisposeStreamProvider


Future<String> helloWorld(Ref ref) async{return 'Hello world';
}------------------------------GENERATED CODE---------------------------------(helloWorld)
final helloWorldProvider = AutoDisposeFutureProvider<Object?>.internal(helloWorld,name: r'helloWorldProvider',debugGetCreateSourceHash:const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash,dependencies: null,allTransitiveDependencies: null,
);

当然,在返回结果使用上会有些差别, 异步的 Provider 会返回一个 AsyncValue ,或者需要 .value 获取一个非空安全的对象:


Widget build(BuildContext context, WidgetRef ref) {final AsyncValue<String> asyncValue = ref.watch(helloWorldProvider);final String? value = ref.watch(helloWorldProvider).value;return MaterialApp(home: Scaffold(appBar: AppBar(title: const Text('Example')),body: Center(child: Text(asyncValue.when(data: (v) => v,error: (_, __) => "error",loading: () => "loading")),),),);
}

函数注解带参数

当你需要给 helloWorld 增加参数的时候,此时的 helloWorldProvider 就不再是一个 AutoDisposeFutureProvider 实例,它将变成 HelloWorldFamily ,它是一个 Family 的实现:


Future<String> helloWorld(Ref ref, String value, String type) async {return 'Hello world $value $type';
}
Widget build(BuildContext context, WidgetRef ref) {final AsyncValue<String> asyncValue = ref.watch(helloWorldProvider("1", "2"));final String? value = ref.watch(helloWorldProvider("1", "2")).value;
}------------------------------GENERATED CODE---------------------------------/// See also [helloWorld].
class HelloWorldFamily extends Family<AsyncValue<String>> {/// See also [helloWorld].const HelloWorldFamily();/// See also [helloWorld].HelloWorldProvider call(String value,String type,) {return HelloWorldProvider(value,type,);}HelloWorldProvider getProviderOverride(covariant HelloWorldProvider provider,) {return call(provider.value,provider.type,);}static const Iterable<ProviderOrFamily>? _dependencies = null;Iterable<ProviderOrFamily>? get dependencies => _dependencies;static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;Iterable<ProviderOrFamily>? get allTransitiveDependencies =>_allTransitiveDependencies;String? get name => r'helloWorldProvider';
}

在 Dart 中,call 方法是一个特殊的方法,它可以让一个类的实例像函数一样调用。

说到 Family , 它的作用是主要就是支持使用额外的参数构建 Provider ,因为前面 helloWorld 需要传递参数,所以 HelloWorldFamily 的主要作用,就是提供创建和覆盖需要参数的 Provider,例如前面的:

  final AsyncValue<String> asyncValue = ref.watch(helloWorldProvider("1", "2"));final String? value = ref.watch(helloWorldProvider("1", "2")).value;

当然,这里你需要注意,不同与前面的 helloWorldProvider 实例,需要参数的 Provider 需要你每次使用时通过参数构建,而此时你每次调用如 helloWorldProvider("1", "2") 都是创建了一个全新实例,如果你需要同一个数据源下 read/watch ,那么你应该在调用时共用一个全局 helloWorldProvider("1", "2") 实例。

如果是不同 Provider 实例,那么你获取到的参数其实是不一样的,因为内部 map 登记的映射关系就是基于 Provider 实例为 key :

不过对比之下,过去你使用 FutureProvider.family 只能覆带一个 Arg 参数,虽然可以通过语法糖传递多个参数,但是终究还是比注解生成的麻烦:

final helloWorldFamily =FutureProvider.family<String, (String, String)>((value, type) async {return 'Hello world $value $type';
});

另外,注解生成时,还会动态生成一个对应的 “Element” ,让 Element 支持获取 Provider 的参数,并实现对应 build 方法,也就是通过 ref 可以获取到相关参数:

mixin HelloWorldRef on AutoDisposeFutureProviderRef<String> {/// The parameter `value` of this provider.String get value;/// The parameter `type` of this provider.String get type;
}class _HelloWorldProviderElementextends AutoDisposeFutureProviderElement<String> with HelloWorldRef {_HelloWorldProviderElement(super.provider);String get value => (origin as HelloWorldProvider).value;String get type => (origin as HelloWorldProvider).type;
}

最后,带参数之后,生成的 _SystemHash 也会根据参数动态变化,从而支持 hotload 等场景:

class _SystemHash {_SystemHash._();static int combine(int hash, int value) {// ignore: parameter_assignmentshash = 0x1fffffff & (hash + value);// ignore: parameter_assignmentshash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));return hash ^ (hash >> 6);}static int finish(int hash) {// ignore: parameter_assignmentshash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));// ignore: parameter_assignmentshash = hash ^ (hash >> 11);return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));}
}

类注解

接着,我们看 @riverpod 除了可以注解函数之后,还可以直接注解 class ,只是 class 需要继承 _$*** 一个子类:


class HelloWorld extends _$HelloWorld {String build() {return 'Hello world';}changeValue(String value) {state = value;}
}
Widget build(BuildContext context, WidgetRef ref) {final String asyncValue = ref.watch(helloWorldProvider);ref.read(helloWorldProvider.notifier).changeValue("next");
}

通过生成代码可以看到,此时生成的是 AutoDisposeNotifierProvider ,也就是在读取时,可以通过 read(****Provider.notifier) 去改变状态:

String _$helloWorldHash() => r'52966cfeefb6334e736061e19443e4c8b94160d8';/// See also [HelloWorld].
(HelloWorld)
final helloWorldProvider =AutoDisposeNotifierProvider<HelloWorld, String>.internal(HelloWorld.new,name: r'helloWorldProvider',debugGetCreateSourceHash:const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash,dependencies: null,allTransitiveDependencies: null,
);typedef _$HelloWorld = AutoDisposeNotifier<String>;

也就是,通过 @riverpod 注解的 class ,是带有 state 状态的 NotifierProvider ,这是对比注解函数最明显的差异

而如果注解 class 需要携带参数,那么可以在 build 上添加需要的参数,最终同样和函数一样会生成一个对应的 HelloWorldFamily


class HelloWorld extends _$HelloWorld {String build(String value, String type) {return 'Hello world';}changeValue(String value) {state = value;}
}

同理,如果你给 build 增加了 async,那么就会生成一个 AutoDisposeAsyncNotifierProviderImpl 的相关实现:


class HelloWorld extends _$HelloWorld {Future<String> build(String value, String type) async {return 'Hello world';}changeValue(String value) {final currentValue = state.valueOrNull ?? "";state = AsyncData(currentValue + value);}removeString(String value) {final currentValue = state.valueOrNull ?? "";state = state.copyWithPrevious(AsyncData(currentValue.replaceAll(value, "")));}
}

可以看到,在注解 class 下可操作空间是在 build ,并且需要注意的是,当你调用 refresh 的时候,State 是会被清空,并且重新调用 build

KeepAlive

那么我们前面说的都是 AutoDispose ,如果我不想他被释放呢?那就是需要用到大写字母开头的 @Riverpod ,给参数配置上 keepAlive: true

(keepAlive: true)
class HelloWorld extends _$HelloWorld 

然后再看输出文件,你就会看到此时 HelloWorldProvider 继承的是 AsyncNotifierProviderImpl 而不是 AutoDispose 了:

class HelloWorldProvider extends AsyncNotifierProviderImpl<HelloWorld, String> {/// See also [HelloWorld].HelloWorldProvider(String value,String type,) : this._internal(

dependencies

另外 @Riverpod 还有另外一个可配置参数 dependencies ,从名字上理解起来是依赖的意思,但是其实它更多用于「作用域」相关的处理。

在 riverpod 里,框架的设计是支持多个 ProviderContainer 的场景,并且每个容器可以覆盖(override)某些 Provider 的数据,例如我只是添加了一个 dependencies: []此时无论列表是否为空,它都可以被认为是一个具有作用域支持的 Provider,从而实现根据上下文进行数据隔离,另外不为空时还可以看作声明 Provider 在作用域内的依赖关系

(dependencies: [])

但是,不是你加了 dependencies 它就自动产生作用域隔离了,不为空时也不会自动追加依赖,它只是一个声明作用,后续还是需要代码配合。

如下代码所示,这里简单的声明了一个带有 dependenciesCounter ,然后:

  • 在页面通过 ref.watch(counterProvider) 监听了 Counter
  • 在新的 dialog 也通过 ref2.watch(counterProvider) 监听了 Counter
(dependencies: [])
class Counter extends _$Counter {int build() => 0;void update(int count) {state = count;}
}class MyApp extends StatelessWidget {Widget build(BuildContext context) {return ProviderScope(child: MaterialApp(home: Consumer(builder: (ctx, ref, __) {final count = ref.watch(counterProvider);return Scaffold(appBar: AppBar(),body: Column(mainAxisAlignment: MainAxisAlignment.start,children: [Text('Counter: $count'),ElevatedButton(onPressed: () {showDialog(context: ctx,builder: (context) => AlertDialog(title: Text('Dialog'),content: Consumer(builder: (_, ref2, __) {final count2 = ref2.watch(counterProvider);return InkWell(onTap: () {ref2.read(counterProvider.notifier).update(count2 + 1);},child: Text('Dialog Counter: $count2'),);})),);},child: Text('Open Dialog'),),],),floatingActionButton: FloatingActionButton(onPressed: () {ref.read(counterProvider.notifier).update(count + 1);}),);}),),);}
}

结果最后运行发现,Dialog 和主页的 Counter 其实还是共享的, dependencies 并没有起到作用:

之所以这样,原因在于没有增加新的 ProviderScope ,如下代码所示,只要将上面的 showDialog 部分修改为如下代码所示:

  • 新增一个 的 ProviderScope
  • 通过 overrides 指定对应 counterProvider
showDialog(context: ctx,builder: (context) => ProviderScope(overrides: [counterProvider,///你还可以 overrideWith 覆盖修改//counterProvider.overrideWith(()=>Counter())],child: AlertDialog(title: Text('Dialog'),content: Consumer(builder: (_, ref2, __) {final count2 = ref2.watch(counterProvider);return InkWell(onTap: () {ref2.read(counterProvider.notifier).update(count2 + 1);},child: Text('Dialog Counter: $count2'),);})),),
);

以上条件缺一不可以,运行后如下图所示,可以看到此时 counterProvider 在主页和 Dialog 之间被有效分割开:

其实原因从源码里也可以看出来,在 ProviderContainer 内部源码我们可以看到,要产生一个独立的作用域,你需要:

  • root 不为空,也就是有一个上级 ProviderContainer
  • 其次存在 dependencies ProviderContainer 的 override 不为空,也就是 dependencies 不为 null 就行,但是 override 必须有 Provider
  • 最后才是返回全新的 _StateReader 用于提供状态数据

所以,从这里就可以看出,dependencies 只是一个先置条件,具体它是不是局部作用域,还得是你用的时候怎么用

同理依赖也是,比如你写了一个 @Riverpod(dependencies: [maxCountProvider]) ,但是你还是需要对应写上 ref.watch(maxCountProvider) ,不然它也并不起作用:

(dependencies: [maxCountProvider])
int limitedCounter(LimitedCounterRef ref) {final max = ref.watch(maxCountProvider); // 监听 return 0.clamp(0, max); 
}

PS ,如果你只是正常监听,不需要作用域的场景,其实直接写 ref.watch 而不需要 dependencies: [maxCountProvider] 也是可以的。

如果我们从输出端看,可以看到有没有 dependencies ,主要就是 _dependencies_allTransitiveDependencies 是否为空的区别:

注意事项

最后也有一些注意事项,例如:

  • 通过注解生成的 Provider 好不要依赖非生成的 Provider,比如这里的 example 是注解,它监听了一个非注解生成的 depProvider ,这样并不规范:

    final depProvider = Provider((ref) => 0);
    void example(Ref ref) {// Generated providers should not depend on non-generated providersref.watch(depProvider);
    }
    
  • 有作用域时,如果监听了某个 Provider ,那么 dependencies 里必须写上依赖 Provider,以下写法就不合规:

    (dependencies: [])
    void example(Ref ref) {// scopedProvider is used but not present in the list of dependenciesref.watch(scopedProvider);
    }
    
  • Provider 里不应该接收 BuildContext

    // Providers should not receive a BuildContext as a parameter.
    
    int fn(Ref ref, BuildContext context) => 0;
    class MyNotifier extends _$MyNotifier {int build() => 0;// Notifiers should not have methods that receive a BuildContext as a parameter.void event(BuildContext context) {}
    }
    

其实类型的注意事项在 riverpod_lint 里都声明了,只是 Custom lint rules 不会直接展示在 dart analyze ,所以需要用户在添加完 riverpod_lint 后,执行对应的 dart run custom_lint

最后

可以看到,通过注解模式,riverpod 可以让开发者少些很多代码,在整体设计理念没有变化的情况下,模版生成的代码会更规范,并且在上层屏蔽了许多复杂度和工作量。

另外通过 dependencies 我们可以可以看到 riverpod 在存储管理上它是统一的,但是在组合上它是分散的的设计理念。

而 Flutter 状态管理一直以来也是「是非之地」,比如近期就出现说 riverpod 在基准性能测试表示不如 signals 的情况,但是作者也回应了该测试属于「春秋笔法」之流:

另外,由于Dart 宏功能推进暂停 ,而 build runner 与数据类的优化还没落地,作者也在探索没有 codegen 下如何也可以便捷使用 riverpod ,比如让 family 支持多个参数:

当然,从作者的维护体验上看,貌似作者又有停滞 codegen 的倾向,看起来左右摇摆的状态还会持续一段时间:

那么, 2025 年 riverpod 还是你状态管理的首选吗

http://www.dtcms.com/wzjs/350198.html

相关文章:

  • 网站安全代维厦门人才网唯一官方网站
  • 阿里云如何建立网站免费检测网站seo
  • 手机网站布局技术微信运营方案
  • 网站建设策划书范文6篇短视频推广平台
  • 网站怎样改logo网络推广的主要工作内容
  • php做网站的源码网络营销师报考条件
  • 怎样做网站的关键词百度软件中心官网
  • 仿百度 wordpress宁波seo推广
  • 有自己的域名怎么建立网站南京网站快速排名提升
  • 陕西省房和城乡建设厅网站免费注册推广网站
  • 网站seo优化报告推广接单平台
  • 天津实用网站建设爱站seo工具
  • 在深圳做网站多少钱百度一下电脑版首页网址
  • 视频网站怎么做压力测试四川百度推广排名查询
  • 网站建设 程序开发搜索推广渠道
  • 厦门网站建设设青岛seo外包服务
  • 专门做生鲜的网站互联网营销师题库
  • vs做动态网站杭州百度百科
  • 大学生做家教网站优化公司组织架构
  • 广州做网站的公司哪家好网络营销的六个特点
  • 社科院网站建设磁力链
  • 网站体验优化武汉网站提升排名
  • 中国电子商务网站建设情况自己做网站难吗
  • 外贸网站建设及推广seo外链
  • 学校门户网站建设方案有什么功能
  • 佛山市门户网站建设接外贸订单的渠道平台哪个好
  • github网站注册账号怎么做网页制作软件免费版
  • WordPress无缩略图文章筛选seo评测论坛
  • 网站开发都需要什么工具有域名了怎么建立网站
  • 天津市武清区建设银行网站如何制作网址