likes
comments
collection
share

Riverpod之Provider(一)

作者站长头像
站长
· 阅读数 63

Riverpod说明

Riverpod是一个优秀的全局状态管理包,官方文档:riverpod.dev/ 。在watch、read之余,也许你好奇背后发生了什么,但你又不想掉头发,那么本篇正好适用你,采用发型守护者版本1.0.4。

flutter_riverpod: 1.0.4

Demo

我们选择一个比计数器还要简单的例子开始,效果就是屏幕中央显示一个“Hello”就完了,没有其它交互。

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Home());
  }
}

final greetingProvider = Provider(((ref) => "Hello"));

class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('example')),
      body: Center(
        child: Consumer(builder: (context, ref, _) {
          final greeting = ref.watch(greetingProvider);
          return Text('$greeting');
        }),
      ),
    );
  }
}

关于ProviderScope和ProviderContainer

组件说明

这里面出现了ConsumerWidget、Consumer、WidgetRef一些新组件,作一下简单说明

  • ConsumerStatefulWidget继承自StatefulWidget,ConsumerState、ConsumerStatefulElement继承各自对应的部分,为了防止画面凌乱,图上没有表现出来
  • 重点是ConsumerStatefulElement实现了WidgetRef,并通过build方法传递到Widget的构建中,这样watch等操作不在话下

Riverpod之Provider(一)

Provider说明

final greetingProvider = Provider(((ref) => "Hello"));

选择Provider开始,是因为它是最简单的一种状态供应商,它只对外提供了访问状态值的接口,外部不能改变其状态值,要获取其状态值,可以使用read或watch方法,它们区别在于

  • read只读取当前状态值,没有其它想法,是个耿直boy
  • watch也读取了当前状态值,不过它不老实,它悄悄的添加了监听,这样无论什么打折优惠它都知道
  • listen 对状态值不感兴趣,重在监听,喜欢贴着墙上网课

按理说,demo中应该使用read方法,因为当前Provider值是不变的,没有优惠,监听是多余的,之所以选择watch,是因为

  • 它的流程可以涵盖read、listen。
  • 虽然外部不能改变状态值,内部是可以的,这里的ref参数后面有说明

WidgetRef的watch过程

  final greeting = ref.watch(greetingProvider);

上面的ref是WidgetRef类型,一个接口,ConsumerStatefulElement是它唯一的实现,所以ref.watch调用就是从ConsumerStatefulElement发起的,下面是首次watch的过程,也是最麻烦的,因为需要初始化、缓存、添加监听。下图是基本流程,没有全面覆盖,有所忽略。 Riverpod之Provider(一)

流程图作为参考,重点有这么几个地方

  1. watch方法,watch方法有以下几种情况
  • 先看_dependencies中有没有缓存target对应的_ProviderSubscription,有的话直接调用其read方法,这个最简单
  • 如果没有的话,看看_oldDependencies有没有,有的话同上
  • 如果也没有,才启动监听流程,并缓存起来,最后也是read一把。注意:监听的回调方法是markNeedsBuild()
ConsumerStatefulElement
Res watch<Res>(ProviderListenable<Res> target) {
    
  return _dependencies.putIfAbsent(target, () {
      final oldDependency = _oldDependencies?.remove(target);

      if (oldDependency != null) {
        return oldDependency;
      }

      return _container.listen<Res>(
        target,
        (_, __) => markNeedsBuild(),//当状态值发生变化就会调用这个方法引起重建
      );
    }).read() as Res;
  }

2. createElement方法,在首次readProviderElement时会调用,这里创建的ProviderElement,是个富二代,啥也没干,都是继承自ProviderElementBase,这个类是流程的重点。createElement方法是一种典型的重武器懒加载,这种设计方式值得借鉴,避免过早而又可能无用的初始化。

Provider
ProviderElement<State> createElement() => ProviderElement(this);

3. create方法,如果你想知道watch、read方法的返回值类型,就直接看这个create方法,当ProviderElement实例化后,需要调用这个方法获取初始化的状态值,通过调用_create函数实现

  • 参数ref就是ProviderElement,或者更通用的说是此Provider的createElement方法的返回值
  • 返回值State和framework的State不是一回事,它是一种范型,但又特指状态值,所以这个名字还是挺符合的
  • _create函数就是greetingProvider中的(ref) => "Hello",所以State类型就是String
Provider
State create(ProviderRef<State> ref) => _create(ref);

4. addListener方法,watch方法中的一个重点就是把前面的监听添加到ProviderElementBase中,这样其持用的状态值发生变化就能通知到我们

ProviderElementBase
  ProviderSubscription<State> addListener(
    ProviderBase<State> provider,
    void Function(State? previous, State next) listener, {
...
  }) {
   ...
    final sub = _ProviderSubscription<State>._(
      this,
      listener,
      onError: onError,
    );

    _listeners.add(sub);

    return sub;
  }

watch过程只需要记住两点

  1. Provider的create方法初始化了状态值,存在ProviderElementBase中,也是watch返回的值
  2. ConsumerStatefulElement在ProviderElementBase中添加了一个 markNeedsBuild()监听,如果state发生变化,会通知监听者,也就是调用markNeedsBuild方法触发build Riverpod之Provider(一)

就上面的Demo来说,那到此可以结束了,但有个很重要的点需要拉出来细说一下,就是上面的ProviderRef参数是什么来头,和WidgetRef很相似啊,难道也能唱、跳、Rap

State create(ProviderRef<State> ref) => _create(ref);

如果查看其接口,发现其实现了Ref接口,里面就有熟悉的watch等方法,但providerRef啥也没干,

abstract class Ref {
... 忽略几百字文档
  T read<T>(ProviderBase<T> provider);
...忽略几百字文档
  T watch<T>(AlwaysAliveProviderListenable<T> provider);
...忽略几百字文档
  RemoveListener listen<T>(
    AlwaysAliveProviderListenable<T> provider,
    void Function(T? previous, T next) listener, {
    bool fireImmediately,
    void Function(Object error, StackTrace stackTrace)? onError,
  });
}

ProviderElement实现了ProviderRef这个接口,同样啥也没干,实现都在ProviderElementBase中

class ProviderElement<State> extends ProviderElementBase<State>
    implements ProviderRef<State> {
...
}

虽然活是ProviderElementBase干的,但为了和上面标题一致,还是用接口的名称,接下来就看下Ref的watch流程

Ref的watch流程

当然,除了watch之外,ProviderElementBase也实现了read和listen,我们同样以watch为例。这些接口的实现意义重大,它意味着不是只有Widget可以对Provider监听或读取,而是Provider之间可以互相监听或读取,现在知道为什么Create函数有个Ref参数了,就是为了方便俊男靓女互相观察

typedef Create<T, R extends Ref> = T Function(R ref);

下图同样是带初始化的基本流程,没有全面覆盖,有所忽略

[图片上传失败...(image-884de8-1684741429114)]

  T watch<T>(ProviderListenable<T> listenable) {
   ...
    final provider = listenable as ProviderBase<T>;
    ...
    final element = _container.readProviderElement(provider);
    _dependencies.putIfAbsent(element, () {
      final previousSub = _previousDependencies?.remove(element);
      if (previousSub != null) {
        return previousSub;
      }

      element._dependents.add(this);

      return Object();
    });

    return element.readSelf();
  }

关键部分如下

  • readProviderElement,这个和前面的一样,初次的时候需要走createElement,以后直接从缓存中取
  • 相互记录依赖的对象,蓝色块的靓仔通过_dependencies记录他依赖了谁,粉色块的美女通过_dependents记录谁依赖了她,后面状态发生变更,_dependents也会通知到
  • 获取粉色element的state值 Riverpod之Provider(一)

这个watch过程相对还是简单的,但具体是如何运作的,下篇用经典的计数器demo来说明,最后补充一下WidgetRef的read、listen的流程图

WidgetRef的read过程

目的:读取ProviderElement中的State, Riverpod之Provider(一)

WidgetRef的listen过程

目的:监听值的变化 Riverpod之Provider(一)

转载自:https://juejin.cn/post/7236286358388768826
评论
请登录