随着 Flutter 的发展,这些年 Flutter 上的状态管理框架如“雨后春笋”般层出不穷,而 「近一年以来最受官方推荐的状态管理框架无疑就是 Riverpod
」 ,甚至已经超过了 Provider
,事实上 Riverpod
官方也称自己为 “Provider
,但与众不同”。
❝
Provider 本身用它自己的话来说是 “InheritedWidget 的封装,但更简单且复用能力更强。” ,而 Riverpod 就是在 Provider 的基础上重构了新的可能。
❞
关于过去一年状态管理框架的对比可以看 《2021 年的 Flutter 状态管理:如何选择?》 , 「本文主要是带你解剖 RiverPod 的内部是如何实现,理解它的工作原理,以及如何做到比 Provider 更少的模板和不依赖 BuildContext 。」
前言
如果说 Riverpod
最明显的特点是什么,那就是外部不依赖 BuildContext
(其实就是换了另外一种依赖形态),因为不依赖 BuildContext
,所以它可以比较简单做到类似如下的效果:
也就是 「Riverpod
中的 Provider
可以随意写成全局,并且不依赖 BuildContext
来编写我们需要的业务逻辑」。
❝
提前声明下,「这里和后续的
Provider
,和第三方库provider
没有关系」。
❞
那 Riverpod
具体内部是怎么实现的呢?接下来让我们开始探索 Riverpod
的实现原理。
❝
Riverpod
的实现相对还是比较复杂,所以还耐心往下看,因为本篇是逐步解析,「所以如果看的过程有些迷惑可以先不必在意,通篇看完再回过来翻阅可能就会更加明朗」。
❞
从 ProviderScope 开始
在 Flutter 里只要使用了状态管理,就一定避不开 InheritedWidget
, Riverpod 里也一样,「在 Riverpod 都会有一个 ProviderScope, 一般只需要注册一个顶级的 ProviderScope。」
❝
如果对于 InheritedWidget 还有疑问,可以看我掘金:《全面理解State与Provider》
❞
先从一个例子开始,如下图所示,是官方的一个简单的例子,可以看到这里:
- 嵌套一个顶级
ProviderScope
; - 创建了一个全局的
StateProvider
; - 使用 ConsumerWidget 的 ref 对创建的
counterProvider
进行read
从而读取State
,获取到int
值进行增加 ; - 使用另一个
Consumer
的ref
对创建的counterProvider
进行watch
,从而读取到每次改变后的int
值;
很简单的例子,「可以看到没有任何 of(context)
, 而全局的 counterProvider
里的数据,就可以通过 ref
进行 read/watch,并且正确地读取和更新。」
❝
那这是怎么实现的呢?counterProvider
又是如何被注入到ProviderScope
里面?为什么没有看到context?
带着这些疑问我们继续往下探索。
❞
首先我们看 ProviderScope
,它是唯一的顶级 InheritedWidget
,所以 counterProvider
必定是被存放在这里:
❝
在 RiverPod 里, 「ProviderScope
最大的作用就是提供一个ProviderContainer
」 。
❞
更具体地说,就是通过内部嵌套的 UncontrolledProviderScope
提供,所以到这里我们可以知道:「ProviderScope 可以往下提供状态共享,因为它内部有一个 InheritedWidget ,而主要往下共享的是 ProviderContainer 这个类」。
所以首先可以猜测:「我们定义的各种 Providers
, 比如上面的 counterProvider
, 都是被存到 ProviderContainer
中,然后往下共享。」
❝
事实上官方对于ProviderContainer
的定义就是:用于保存各种 Providers 的 State ,并且支持 override 一些特殊 Providers 的行为。
❞
ProviderContainer
这里出现了一个新的类,叫 ProviderContainer
,其实一般情况下使用 RiverPod 你都不需要知道它,「因为你不会直接操作和使用它,但是你使用 RiverPod 的每个行为都会涉及到它的实现」,例如 :
-
ref.read
会需要它的Result read<Result>
; -
ref.watch
会需要它的ProviderSubscription<State> listen<State>
; - ref.refresh 会需要它的
Created refresh<Created>
就算是各种Provider
的保存和读取基本也和它有关系,所以它作为一个对各种Provider
的内部管理的类,实现了RiverPod
里很关键的一些逻辑。
“Provider” 和 “Element”
那前面我们知道 ProviderScope
往下共享了 ProviderContainer
之后,「Provider 又是怎么工作的呢?为什么 ref.watch/ ref.read 会可以读取到它 Provider 里的值?」
继续前面的代码,这里只是定义了 StateProvider
,并且使用了 ref.watch
,为什么就可以读取到里面的 state
值?
首先 StateProvider
是一个特殊的 Provider
,在它的内部还有一个叫 _NotifierProvider
的帮它实现了一层转换,「所以我们先用最基础的 Provider 类作为分析对象」。
基本是各种类似的 Provider
都是 ProviderBase
的子类,所以我们先解析 ProviderBase
。
在 RiverPod 内部,「每个 ProviderBase
的子类都会有其对应的 ProviderElementBase
子类实现」 ,例如前面代码使用的 StateProvider
是 ProviderBase
的之类,同样它也有对应的 StateProviderElement
是 ProviderElementBase
的子类;
「所以 RiverPod 里基本是每一个 “Provider” 都会有一个自己的 “Element” 。」
❝
「这里的 “Element” 不是 Flutter 概念里三棵树的 Element,它是 RiverPod 里 Ref 对象的子类」。Ref 主要提供 RiverPod 内的 “Provider” 之间交互的接口,并且提供一些抽象的生命周期方法,所以它是 RiverPod 里的独有的 “Element” 单位。
❞
那 “Provider” 和 “Element” 的作用是什么?
首先,在上面例子里我们**「构建 StateProvider
时传入的 (ref) => 0
,其实就是 Create<State, StateProviderRef<State>>
」** 函数,我们就从这个 Create
函数作为入口来探索。
Create<T, R extends Ref> = T Function(R ref)
RiverPod 里构建 “Provider” 时都会传入一个 Create
函数,而这个函数里一遍我们会写一些需要的业务逻辑,比如 counterProvider
里的 ()=> 0
就是初始化时返回一个 int
为 0 的值,「更重要的是决定了 State
的类型」。
如果在上面代码的基础上增加了 <int>
就更明显,「事实上前面我们一直在说的 State
就是一个泛型,而我们定义 “Provider” 就需要定义这个泛型 State
的类型,比如这里的 int
」 。
回归到普通 Provider
的调用,「我们传入的 Create 函数,其实就是在 ProviderElementBase
里被调用执行」。
如上图所示,简单来说当 「ProviderElementBase 执行 “setState” 时,就会调用 Create 函数,从而执行获取到我们定义的泛型 State,得到 Result 然后通知并更新 UI」。
❝
这里的 “setState” 也不是 Flutter Framework 里的 setState ,而是 RiverPod 内自己首先的一个 “setState” 函数,和
Flutter
框架里的State
无关。
❞
所以每个 “Provider” 都会有自己的 “Element” ,而构建 “Provider” 时是传入的 Create 函数会在 “Element” 内通过 setState
调用执行。
「“Element” 里的 setState
主要是通过新的 newState 去得到一个 RiverPod 里的 Result
对象,然后通过 _notifyListeners
去把得到 Result
更新到 watch
的地方。」
Result
的作用主要是通过 Result.data
、Result.error
、 map
和 requireState
等去提供执行结果,一般情况下状态都是通过 requireState
获取,具体在 RiverPod
体现为:
❝
「我们调用 read() 时,其实最后都调用到 element.readSelf(); ,也就是返回 requireState」 (其实一般也就是我们的泛型 State) 。
❞
是不是有点乱?
简单点理解就是:构建出 “Provider” 之后, “Element” 里会执行setState(_provider.create(this))
; 调用我们传入的 Create
函数,并把 “Element” 自己作为 ref
传入进入,「所以我们使用的 ref
其实就是 ProviderElementBase
」。
❝
所以 RiverPod 里的起名是有原因的,这里的 “Provider” 和 “Element” 的关系就很有 Flutter 里Widget
和Element
的即视感。
❞
分步骤来说就是:
- 构建 Provider 时我们传入了一个 Create 函数;
- Create 函数会被 ProviderElementBase 内部的 setState 所调用,得到一个 Reuslt;
- Reuslt 内的 requireState 就可以让我们在使用 read() 的时候,获取到我们定义的 泛型 State 的值。
WidgetRef
前面介绍了那么多,但还是没有说 StateProvider
怎么和 ProviderScope
关联到一起,也就是 “Provider” 怎么和 ProviderContainer
关联到一起,「凭什么 ref.read
就可以读到 State
?」
那么前面代码里,我们用到的 ConsumerWidget
和 Consumer
都是同个东西,「而这个 ref 就是前面我们一直说的 “Element” ,或者说是 ProviderElementBase
」 。
在源码里可以看到, ConsumerWidget
的逻辑主要在 ConsumerStatefulElement
, 而ConsumerStatefulElement
继承了 StatefulElement
,并实现了 WidgetRef
接口。
如上代码就可以看到前面很多熟悉的身影了: ProviderScope
、ProviderContainer
、 WidgetRef
。
首先我们看 ProviderScope.containerOf(this)
,终于看到我们熟悉的 BuildContext
有没有,「这个方法其实就是以前我们常用的 of(context)
,但是它被放到了 ConsumerStatefulElement
使用,用于获取 ProviderScope
往下共享的 ProviderContainer
」。
所以我们看到了,ConsumerWidget
里的 ConsumerStatefulElement
获取到了 ProviderContainer
,所以 「ConsumerStatefulElement
可以调用到 ProviderContainer
的 read/watch」 。
然后回过头来看,ConsumerStatefulElement
实现了 WidgetRef
接口,所以 我们使用的 WidgetRef
就是 ConsumerStatefulElement
本身
「也就是 ref.read 就是执行 ConsumerStatefulElement 的 read , 从而执行到 ProviderContainer 的 read。」
所以我们可以总结: 「BuildContext 是 Element , 然后 Element 又实现了 WidgetRef ,所以此时的 WidgetRef 就是 BuildContext 的替代」 。
❝
这里不要把 Flutter 的 Element 和 RiverPod 里的 “ProviderElementBase” 搞混了。
❞
所以WidgetRef
这个接口成为了Element
的抽象,替代了BuildContext
,所以这就是 Riverpod 的“魔法”之一 。
read
所以前面我们已经理清了 ProviderScope
、 Provider
、 ProviderElementBase
、 ProviderContainer
、 ConsumerWidget(ConsumerStatefulElement)
和 WidgetRef
等的关系和功能,那最后我们就可以开始理清楚 read
的整个工作链条。
我们理清和知道了 的概念与作用之后,结合 ref.read
来做一个流程分析,那整体就是:
- ConsumerWidget 会通过内部的
ConsumerStatefulElement
获取到顶层ProviderScope
内共享的ProviderContainer
; - 当我们通过
ref
调用read/watch
时,其实就是通过ConsumerStatefulElement
去调用 ProviderContainer
内的read
函数;
那最后就是 ProviderContainer
内的 read
函数如何读取到 State
?
这就要结合前面我们同样介绍过的 ProviderElementBase
, 事实上 ProviderContainer
在执行 read
函数时会调用 readProviderElement
。
readProviderElement
顾名思义就是通过 Provider
去获取到对应的 Element
,例如 :
ref.read(counterProvider),
一般情况下 read/watch 简单来说就是从 ProviderContainer
里用 proivder
做 key 获取得到 ProviderElementBase
这个 “Element”,「这个过程又有一个新的对象需要简单介绍下,就是:_StateReader」。
readProviderElement
其中一个关键就是获取 _StateReader
,在 ProviderContainer
里有一个 _stateReaders
的内部变量,它就是用于缓存 _StateReader
的 Map 。
所以在 ProviderContainer
内部:
- 1、首先会根据
read
时传入的provider
构建得到一个_StateReader
; - 2、以
provider
为 key ,_StateReader
为 value 存入_stateReaders
这个 Map,并返回_StateReader
; - 3、通过
_StateReader
的getElement()
获取或者创建到ProviderElementBase
;
❝
这里的以ProviderBase
为 Key ,_StateReader
为 value 存入_stateReaders
,「其实就是把 “provider” 存入到了ProviderContainer
,也就是和 ProviderScope 关联起来,也就是自此 “provider” 和ProviderScope
就绑定到一起」。
❞
没用使用到明面上的 BuildContext
和多余的嵌套,就让 Provider
和 ProviderScope
关联起来。另外这里可以看到,「在 ref.read
时,如何通过 provider
构建或者获取到 ProviderElementBase
」。
得到 ProviderElementBase
还记得前面我们介绍 “Provider” 和 “Element” 的部分吗?ProviderElementBase
会调用 setState
来执行我们传入的 Create
函数,得到 Result
返回 State
。
可以看到,这里获取的 ProviderElementBase
之后 return element.readSelf()
,其实就是返回了 requireState
。
「自从整个 RiverPod 里最简单的 ref.read 流程就全线贯通了」:
-
ProviderScope
往下共享ProviderContainer
; -
ConsumerWidge
t 内部的ConsumerStatefulElement
通过BuildContext
读取到ProviderContainer
, 并且实现WidgetRef
接口; -
通过
WidgetRef
接口的read(provider)
调用到ProviderContainer
里的read
; -
ProviderContainer
通过read
方法的provider
创建或者获取得到ProviderElementBase
-
ProviderElementBase
会执行provider
里的Create
函数,来得到Result
返回State
;
其他的watch,refresh
流程大同小异,就是一些具体内部实现逻辑更复杂而已,比如刷新时:
❝
通过 ref.refresh 方法, 其实触发的就是ProviderContainer
的 refresh,然后最终还是会通过 _buildState 去触发 setState(_provider.create(this)) 的执行。
❞
「而从这个流程分析,也看到了 RiverPod 如何不暴露使用 BuildContext 实现全线关联的逻辑」。
额外分析
前面基本介绍完整个调用流程,这里在额外介绍一些常见的调用时如何实现,比如在 Riverpod 里面会看到很多 “Element” ,比如 ProviderElement
、StreamProviderElement
、 FutureProviderElement
等这些 ProviderElementBase
的子类。
我们结果过它们并不是 Flutter 里的 Element ,而是 Riverpod 里的的 State 单位,用于处理 Provider 的状态,比如 「FutureProviderElement
就是在 ProviderElementBase
的基础上提供一个 AsyncValue<State>
,主要在 FutureProvider
里使用」。
AsyncValue
在 RiverPod 里正常情况下的 create 方法定义是如下所示:
而在 FutureProvider
下是多了一个 _listenFuture
,这个 Function 执行后的 value
就会是 AsyncValue<State>
的 State
类型。
从 _listenFuture
的执行上看, 内部会对这个 future()
执行,会先进入 AsyncValue<State>.loading()
之后,根据 Future
的结果返回决定返回AsyncValue<State>.data
或者 AsyncValue<State>.error
。
「所以比如在 read / watch 时,返回的泛型 requireState 其实变成了 AsyncValue」。
而针对 AsyncValue
官方做了一些 extension
,在 AsyncValueX
上,其中出了获取 AsyncData
的data
\ asData
和 T value 之外,最主要提供了起那么所说的不同状态的构建方法,比如 when
方法:
autoDispose & family
在 Riverpod 里应该还很常见一个叫 autoDispose
和 family
的静态变量,几乎每个 Provider
都有,又是用来干什么的呢?
举个例子,前面代码里我们有个 FutureProvider
, 我们用到了里的 autoDispose
:
「其实 FutureProvider.autoDispose
主要就是 AutoDisposeFutureProvider
,以此类推基本每个 Provider
都有自己的 autoDispose
实现,family
也是同理」。
如果说正常的 Provider
是继承了 AlwaysAliveProviderBase
,那 AutoDisposeProvider
就是继承于 AutoDisposeProviderBase
:
从名字可以看出来:
-
AlwaysAliveProviderBase
是一只活跃的; -
AutoDisposeProviderBase
自然就是不listened
的时候就销毁;
也就是内部_listeners
、_subscribers
、_dependents
都是空的时候,当然它还有另外一个 maintainState 的控制状态,默认它就是 false 的时候,就可以执行销毁。
❝
「简单理解就是用“完即焚烧” 。」
❞
比如前面我们介绍调用 read
的时候,都会调用 mayNeedDispose
去尝试销毁:
❝
销毁也就是调用element.dispose()
和从_stateReaders
这个 map 里移除等等。
❞
同样的 family
对应是 ProviderFamily
,它的作用是:「使用额外的参数构建 provider ,也即是增加一个参数」。
例如默认是把 :
final tagThemeProvider = Provider<TagTheme>
可以变成
final tagThemeProvider2 = Provider.family<TagTheme, Color>
然后你就可以使用额外的参数,在 read/watch
的时候 :
final questionsCountProvider = Provider.autoDispose((ref) {
return ref
.watch(tagThemeProvider2(Colors.red));
});
之所以可以实现这个功能,就要看它的实现 ProviderFamily
,对比一般 Provider
默认的 create
,ProviderFamily
的是:
可以看到 create
的是新的一个 Provider
,「也就是 family
下其实是 Provider
嵌套 Provider
」。
所以从上面的例子出发,以前我们是通过 ref.watch(tagThemeProvider)
;就可以了,因为我们的 tagThemeProvider
的直接就是 ``ProviderBase。
但是如果使用 ref.watch(tagThemeProvider2); 就会看到错误提示
The argument type 'ProviderFamily<TagTheme, Color>' can't be assigned to the parameter type 'ProviderListenable<dynamic>'.
是的,因为这里是 Provider
嵌套 Provider
,我们先得到是的 ProviderFamily<TagTheme, Color>
,所以我们需要改为 ref.watch(tagThemeProvider2(Colors.red));
。
「通过 tagThemeProvider2(Colors.red)
执行一次变为我们需要的 ProviderBase
」。
那 tagThemeProvider2
这个 ProviderFamily
为什么是这样执行? ProviderFamily
明明没有这样的构造函数。
❝
这就涉及到 Dart 语言的特性,如果有兴趣可以看 :Dart 里的类型系统 - 掘金
❞
首先这里拿到的是一个ProviderFamily<TagTheme, Color>
,在 Dart 中所有函数类型都是Function
的子类型,所以函数都固有地具有call
方法。
我们执行 tagThemeProvider2(Colors.red)
其实就是执行了 ProviderFamily
得 call
方法,从而执行了 create
方法,得到 FamilyProvider<State>
,FamilyProvider
也就是 ProviderBase
的子类 。
❝
注意这里有点容易看错的地方,「一个是
ProviderFamily
, 一个是FamilyProvider
」, 我们从ProviderFamily
里面得到了FamilyProvider
, 作为ProviderBase
给ref.watch
。
❞
最后
很久没有写这么长的源码分析了,不知不觉就写到了半夜凌晨,其实相对来说,整个 Riverpod
更加复杂,所以阅读起来也更加麻烦,但是使用起来反而会相对更便捷,特别是**「没有了 BuildContext
的限制,但是同时也是带来了 ConsumerWidget
的依赖,所有利弊只能看你自己的需求,但是整体 Riverpod
肯定是一个优秀的框架,值得一试。」**