在各种前端开发中,由于状态管理对于App的开发维护成本,性能等方面都起着至关重要的作用,所以选择合适的状态管理框架显得尤为重要。Flutter
作为跨平台框架的后起之秀,背靠Google
大树,短时间内开发者们在开源社区提供了多种状态管理框架。而Provider
是官方推荐的状态管理方式之一,可用作跨组件的数据共享。本文将针对Provider
框架的使用及实现原理作详细的说明,并在最后对主流的状态管理框架进行比较。
Provider
的使用非常简单,通常使用ChangeNotifierProvider
配合ChangeNotifier
一起使用来实现状态的管理与Widget的更新。其中ChangeNotifier
是系统提供的,用来负责数据的变化通知。ChangeNotifierProvider
本质上其实就是Widget
,它作为父节点Widget
,可将数据共享给其所有子节点Widget
使用或更新。具体的原理解析在后续章节会进行说明。所以通常我们只需要三步即可利用Provider
来实现状态管理。
1.创建混合或继承ChangeNotifier
的Model
,用来实现数据更新的通知并监听数据的变化。
2.创建ChangeNotifierProvider
,用来声明Provider
,实现跨组建的数据共享。
3.接收共享数据。
我们来举个例子,看看它是怎么在父子之间进行数据共享的:
Model
class ProviderViewModel with ChangeNotifier {
int _number = 0;
get number => _number;
void addNumber() {
_number++;
notifyListeners();
}
}
上面的代码很简单,调用addNumber()
方法让_number
加1,并调用notifyListeners()
通知给监听方。
ChangeNotifierProvider
class ProviderTestPage extends StatelessWidget {
final _providerViewModel = ProviderViewModel();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Provider Test"),
),
body: ChangeNotifierProvider.value(
value: _providerViewModel,
builder: (context, child) {
return Column(
children: [
const Text("我是父节点"),
Text(
"Parent number is: ${Provider.of<ProviderViewModel>(context).number}"),
ChildA(),
//ChildB(),
//ChildC()
],
);
},
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_providerViewModel.addNumber();
}, //使用context.read不会调用rebuild
),
);
}
}
我们用ChangeNotifierProvider
将父布局包裹,在父或子节点ChildA
通过Provider.of<T>(BuildContext context, {bool listen = true})
进行数据操作,可同步更新父与子的数据与UI。其中listen
默认为true
可监听数据的变化,为false
的情况只可读取数据的值。
class ChildA extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("childA build");
return Container(
width: double.infinity,
color: Colors.amberAccent,
child: Column(
children: [
Text(
"Child A number: ${Provider.of<ProviderViewModel>(context).number}"),
MaterialButton(
child: const Text("Add Number"),
color: Colors.white,
onPressed: () {
Provider.of<ProviderViewModel>(context, listen: false)
.addNumber();
})
],
),
);
}
}
我们来看一下效果:
我们可以看到不管是在父节点还是在子节点,都可以对ProviderViewModel
的数据进行操作和监听。例1在操作与读取时使用的是Provider.of<T>(BuildContext context, {bool listen = true})
的方式,为了可以更明确对于Provider
的操作,我们可将它替换为context.watch<>()和context.read<>()
方式。 我们可以通过源码看到,context.watch<>()
和context.read<>()
方法其实都是调用Provider.of<T>(BuildContext context, {bool listen = true})
来实现的:
T watch<T>() {
return Provider.of<T>(this);
}
T read<T>() {
return Provider.of<T>(this, listen: false);
}
语义更加清晰明确。 如:
class ChildB extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("childB build");
return Container(
width: double.infinity,
color: Colors.red,
child: Column(
children: [
const Text("我是子节点"),
Text("Child B number: ${context.watch<ProviderViewModel>().number}"),
MaterialButton(
child: const Text("Add Number"),
color: Colors.white,
onPressed: () {
context.read<ProviderViewModel>().addNumber();
})
],
),
);
}
}
ChildB
与ChildA
实际上是一致的。我们把ProviderTestPage
的ChildB()
放开:
其中,每点击一次父Widget
右下角的加号或子Widget
的Add Number按钮,我们看一下Log
打印的结果:
我们会发现每一次的操作,都会导致ChildA
与ChildB
整体重新build
。但实际上从代码中我们可知,在ChildA
和ChildB
中,只有以下的Text()
会监听ProviderViewModel
的数据更新:
//ChildA:
Text("Child A number: ${Provider.of<ProviderViewModel>(context).number}")
//ChildB:
Text("Child B number: ${context.watch<ProviderViewModel>().number}")
那么我们希望可以实现局部的更新该如何实现?Flutter
提供了Consumer<>()
来进行支持。下面我们来看一下Consumer<>()
的用法:
class ChildC extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("childC build");
return Container(
width: double.infinity,
color: Colors.blue,
child: Column(
children: [
const Text("我是子节点"),
Consumer<ProviderViewModel>(builder: (context, value, child) {
print("ChildC Consumer builder");
return Text("Child C number: ${value.number}");
}),
MaterialButton(
child: const Text("Add Number"),
color: Colors.white,
onPressed: () {
context.read<ProviderViewModel>().addNumber();
})
],
),
);
}
}
由于我们只希望Text()
来监听ProviderViewModel
的数据更新,我们用Consumer<>()
包裹住Text()
,其中builder
的传参value
即是ProviderViewModel
对象,把ProviderTestPage
的ChildC()
放开,我们看一下结果:
再打印一下Log
:
从Log
中我们可以得知,ChildC
并没有被rebuild
,而是由Consumer
调用内部的builder
来实现局部更新的。 到此为止,一个简单的Provider
使用就介绍完成。另外Provider
还提供了ProxyProvider
,从名字上来看,我们可知这是个代理Provider,它是用来协调Model与Model之间的更新,比如一个ModelA依赖另一个ModelB,ModelB更新,他就要让依赖它的ModelA也随之更新。我们直接上代码来看一下它的用法,还是分三步:
ProxyProviderViewModel
:class ProxyProviderViewModel with ChangeNotifier {
int number;
ProxyProviderViewModel(this.number);
String get title {
return "The number is: $number";
}
}
这个类只是简单的在构造方法例传入int值,并创建get方法得到一段文本。
Provider
:class ProxyProviderTestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Provider Test"),
),
body: MultiProvider(
providers: [
ChangeNotifierProvider<ProviderViewModel>(
create: (_) => ProviderViewModel()),
ChangeNotifierProxyProvider<ProviderViewModel,
ProxyProviderViewModel>(
create: (context) => ProxyProviderViewModel(
context.read<ProviderViewModel>().number),
update: (context, providerViewModel, proxyProviderViewModel) =>
ProxyProviderViewModel(providerViewModel.number))
],
builder: (context, child){
return Column(
children: [
ChildProxy(),
MaterialButton(
child: const Text("Add Number"),
color: Colors.amberAccent,
onPressed: () {
context.read<ProviderViewModel>().addNumber();
})
],
);
},
),
);
}
}
我们在body
中用MultiProvider
来包裹布局。MultiProvider
的作用是同时可声明多个Provider
供使用,为参数providers
添加Provider
数组。我们首先声明一个ChangeNotifierProvider
,同例1中的ProviderViewModel
。接着我们声明一个ChangeNotifierProxyProvider
用来做代理Provider
。其中create
参数是ProxyProvider
的创建,update
参数是ProxyProvider
的更新。在我们的例子中,实际上是对ProviderViewModel
进行数据操作,由ProxyProviderViewModel
监听ProviderViewModel
的数据变化。
class ChildProxy extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: Column(
children: [
Text(context.watch<ProxyProviderViewModel>().title),
],
),
);
}
}
在ChildProxy
中,我们监听ProxyProviderViewModel
的数据变化,并将title
显示在Text()
中,进行UI上的更新。 我们看一下效果:
我们调用context.read<ProviderViewModel>().addNumber()
对ProviderViewModel
的数据进行更新,可同时更新ProxyProviderViewModel
的number
对象,而ChildProxy
由于监听了ProxyProviderViewModel
的数据变化,会因此更新UI中title
的内容。
好了,到此为止,Provider
的使用介绍到这里,下面我们将针对Provider
的原理进行说明。Provider
实际上是对InheritedWidget
进行了封装,它才是真正实现父与子数据共享的重要元素,所以为了理清Provider
的原理,我们必须先弄清楚InheritedWidget
的实现过程。
InheritedWidget
提供了沿树向下,共享数据的功能,系统中的Provider
,Theme
等实现都是依赖于它。弄清楚它的原理,对于理解Flutter
的数据共享方式会有很大的帮助。本章节将先通过实例说明InheritedWidget
的用法,然后进行原理的解析。
我们举个简单的例子:
这是一个简单的树结构,其中ChildA
和ChildC
需要共享Data
这个数据,ChildB
不需要。传递方式有很多种,比如说通过构造方法传递,通过函数调用,函数回调传递等。但是如果树层级非常多的话,刚才提到的传递方式将会对整个代码结构带来灾难,包括代码耦合度过高,回调地狱等。我们看看InheritedWidget
是怎么处理的:
1.作为整个树的父节点,需要使ChildA
继承InheritedWidget
:
class ChildA extends InheritedWidget {
int number;
ChildA({required Widget child, required this.number}) : super(child: child);
static ChildA? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ChildA>();
}
@override
bool updateShouldNotify(covariant ChildA oldWidget) {
return oldWidget.number != number;
}
}
updateShouldNotify()
方法需要被重写,用来判断现有共享数据和旧的共享数据是否一致,是否需要传递给已注册的子组件。of()
方法是一种约定俗成的通用写法,只是起到方便调用的作用。其中context.dependOnInheritedWidgetOfExactType()
是为它的子组件注册了依赖关系。 通过这样的方式,将ChildA
声明成了一个给子组件共享数据的Widget
。ChildB
就是一个中间层级的普通Widget
,用来连接树结构:
class ChildB extends StatefulWidget {
final Widget child;
ChildB({Key? key, required this.child}) : super(key: key);
@override
_ChildBState createState() => _ChildBState();
}
class _ChildBState extends State<ChildB> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("ChildB didChangeDependencies");
}
@override
Widget build(BuildContext context) {
print("ChildB build");
return Container(
width: double.infinity,
color: Colors.amberAccent,
child: Column(
children: [const Text("我是子节点 ChildB"), widget.child],
),
);
}
}
ChildC
依赖ChildA
,并读取ChildA
的共享数据,代码如下:
class ChildC extends StatefulWidget {
ChildC({Key? key}) : super(key: key);
@override
_ChildCState createState() => _ChildCState();
}
class _ChildCState extends State<ChildC> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("ChildC didChangeDependencies");
}
@override
Widget build(BuildContext context) {
print("ChildC build");
return Container(
width: double.infinity,
color: Colors.red,
child: Column(
children: [
const Text("我是子节点 ChildC"),
Text("Child C number: ${ChildA.of(context)?.number}"),
],
),
);
}
}
ChildC
通过ChildA.of(context)?.number
的方式读取ChildA
的共享数据。 我们把这个树串起来:
class InheritedWidgetTestPage extends StatefulWidget {
InheritedWidgetTestPage({Key? key}) : super(key: key);
@override
_InheritedWidgetTestPageState createState() =>
_InheritedWidgetTestPageState();
}
class _InheritedWidgetTestPageState extends State<InheritedWidgetTestPage> {
int _number = 10;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("InheritedWidget Test"),
),
body: ChildA(
number: _number,
child: ChildB(
child: ChildC(),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_number++;
});
},
),
);
}
}
在点击floatingActionButton的
时候,修改_number
的值,通过构造方法传递给ChildA
,整个树重新build
。ChildC
读取ChildA
的数据,并进行UI的更新,我们看一下结果:
ChildC
接收到了数据变化并进行了更新,我们再来看一下Log
:
在这个过程中,会发现ChildB
和ChildC
都进行了rebuild
,由于ChildC
依赖ChildA
的共享数据,ChildC
在rebuild
之前执行了didChangeDependencies()
方法,说明ChildC
的依赖关系发生了改变;而ChildB
由于不依赖ChildA
的共享数据所以并没有执行didChangeDependencies()
。
这个例子给出了InheritedWidget
的一个基本使用方式,但需要注意的是,在整个树结构中,其实ChildB
是不依赖ChildA
的共享数据的,按理来说,在数据发生变化,我们是不希望ChildB
进行rebuild
的。所以需要说明的是,InheritedWidget
的正确用法并不是通过setState()
来实现rebuild
的,这里用setState()
举例仅仅是为了将整个流程串起来。这个例子的重点在于,依赖父组件的共享数据的子组件,将在生命周期中执行didChangeDependencies()
方法。我们可以通过ValueNotifier
+ValueListenable
来进行局部的更新,这部分出离了本文的内容,先不作展开。
接下来我们分析一下InheritedWidget
是如何实现父与子之间的数据共享的。
为了实现父与子的数据共享,我们需要弄清楚两件事:
我们先来看一下InheritedWidget
这个类:
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key? key, required Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
InheritedWidget
继承ProxyWidget
,最终继承的是Widget
。它只有两个方法,一个是updateShouldNotify()
,在上面的例子中可知是用来判断现有共享数据和旧的共享数据是否一致,是否需要传递给已注册的子组件的。另外还重写了createElement()
方法,创建一个InheritedElement
对象。InheritedElement
最终继承Element
,我们先看一下它的结构:
从命名中我们就可知setDependencies()
是用来绑定依赖关系的。接下来我们从子组件获取InheritedWidget
实例开始看起,看看具体的绑定流程。如实例中的如下代码:
static ChildA? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ChildA>();
}
我们看一下context.dependOnInheritedWidgetOfExactType<ChildA>()
的流程:
//BuildContext
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
//Element
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
真正的实现是在Element
中进行的。其中_inheritedWidgets
是个Map
,key
为T
的类型。从上面代码我们可以知道,先从_inheritedWidgets
里寻找类型为T
的InheritedElement
,即父的InheritedElement
。_updateInheritance()
是在mount()
和activate()
调用的,_inheritedWidgets
的初始化在子类InheritedElement
的_updateInheritance()
中的实现如下:
//InheritedElement
@override
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets![widget.runtimeType] = this;
}
在Element
中的实现如下:
//Element
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
从上面的代码我们可以得知,普通Element
组件在生命周期的初始阶段,它的_inheritedWidgets
为父组件的_inheritedWidgets
。而_inheritedWidgets
的InheritedElement
,会将自己添加到_inheritedWidgets
中,从而通过此方式将组件和InheritedWidgets
的依赖关系层层向下传递,每一个Element
中都含有_inheritedWidgets
集合,此集合中包含了此组件的父组件且是InheritedWidgets
组件的引用关系。接下来我们回到dependOnInheritedWidgetOfExactType()
方法,ancestor
已经被加到_inheritedWidgets
中,所以它不为空,我们继续看里面dependOnInheritedElement()
的实现:
//Element
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
//InheritedElement
@protected
void updateDependencies(Element dependent, Object? aspect) {
setDependencies(dependent, null);
}
//InheritedElement
@protected
void setDependencies(Element dependent, Object? value) {
_dependents[dependent] = value;
}
好到此为止,我们证实了之前的猜测,子组件找到InheritedElement
类型的父组件,父组件调用setDependencies()
,为子组件向_dependents
中添加注册, InheritedWidget
组件更新时可以根据此列表通知子组件。将以上过程总结一下,如下图:
InheritedElement
的初始阶段:mount()
和activate()
的时候调用_updateInheritance()
方法将自己添加到_inheritedWidgets
中。其他Element
子组件会直接拿父组件的_inheritedWidgets
。context.dependOnInheritedWidgetOfExactType<>()
时,将自己注册给_inheritedWidgets
中获取的InheritedElement
类型的父组件的 dependents
中,从而实现了依赖关系的确定。接下来我们看一下当组件发生变化时,父通知子的方式。
在实例中,当setState()
发生数据改变的时候,经过一系列处理后,会走到InheritedElement
的updated()
方法中去:
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
当执行了我们自定义InheritedWidget
的updateShouldNotify()
判断现有共享数据和旧的共享数据是否一致需要更新后,继续执行父类ProxyElement
的updated()
方法:
//ProxyElement
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
//InheritedElement
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final Element dependent in _dependents.keys) {
assert(() {
// check that it really is our descendant
Element? ancestor = dependent._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies!.contains(this));
notifyDependent(oldWidget, dependent);
}
}
从这段代码中我们可以看出,在notifyClients()
中会对_dependents
的key
进行遍历,然后执行notifyDependent()
进行通知。接着我们看notifyDependent()
都做了什么:
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
@mustCallSuper
void didChangeDependencies() {
assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}
它调用了每个dependent
的didChangeDependencies()
方法,来通知InheritedWidget
依赖发生了变化,当前element
需要被标记为dirty
,重新进行build
。到此为止,完成了当数据发生变化时,父通知子的流程。我们看一下父通知子的流程图:
总结一下就是当InheritedElement
数据发生变化而更新的时候,父InheritedWidget
会遍历_dependents
,子会执行didChangeDependencies()
方法将子组件标记为dirty
而重新build
。
了解了InheritedWidget
的实现后,我们下个章节对Provider
进行解析。
接下来我们分析一下Provider
的实现。还记着文章开头,我们说明需要三步来利用Provider
来实现状态管理。
1.创建混合或继承ChangeNotifier
的Model
,用来实现数据更新的通知并监听数据的变化。
2.创建ChangeNotifierProvider
,用来声明Provider
,实现跨组建的数据共享。
3.接收共享数据。
我们从创建Model
开始讲起:
ChangeNotifier
实现了Listenable
接口,而Listenable
实际上就是一个观察者模型。我们先来看一下ChangeNotifier
的结构:
在ChangeNotifier
里维护了一个_listeners
对象,通过addListener()
和removeListener()
进行添加或删除。在调用notifyListeners()
的时候将数据通知给_listeners
。ChangeNotifier
非常简单,我们接着来分析ChangeNotifierProvider
的实现。
ChangeNotifierProvider
继承了多个层级:ListenableProvider
->InheritedProvider
->SingleChildStatelessWidget
->StatelessWidget
,实际上它是个StatelessWidget
。我们从ChangeNotifierProvider.value()
方法开始:
//ChangeNotifierProvider
ChangeNotifierProvider.value({
Key? key,
required T value,
TransitionBuilder? builder,
Widget? child,
}) : super.value(
key: key,
builder: builder,
value: value,
child: child,
);
其中required T value
是ChangeNotifier
对象,我们继续看super()
的调用:
//ListenableProvider
ListenableProvider.value({
Key? key,
required T value,
UpdateShouldNotify<T>? updateShouldNotify,
TransitionBuilder? builder,
Widget? child,
}) : super.value(
key: key,
builder: builder,
value: value,
updateShouldNotify: updateShouldNotify,
startListening: _startListening,
child: child,
);
static VoidCallback _startListening(
InheritedContext e,
Listenable? value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
ListenableProvider
创建了一个VoidCallback
对象,其中value
是个Listenable
对象,就是我们传入的ChangeNotifier
对象。它的实现是为ChangeNotifier
添加listener
,这个listener
将会执行InheritedContext.markNeedsNotifyDependents()
方法,这个我们之后再做讨论。总而言之,ListenableProvider
的作用就是帮我们为ChangeNotifier
添加了listener
。我们接着往下看:
//InheritedProvider
InheritedProvider.value({
Key? key,
required T value,
UpdateShouldNotify<T>? updateShouldNotify,
StartListening<T>? startListening,
bool? lazy,
this.builder,
Widget? child,
})
: _lazy = lazy,
_delegate = _ValueInheritedProvider(
value: value,
updateShouldNotify: updateShouldNotify,
startListening: startListening,
),
super(key: key, child: child);
到了InheritedProvider
这一层,我们发现builder
没有被继续传下去了,InheritedProvider
持有了一个_ValueInheritedProvider
类型的_delegate
。它的父类_Delegate
的代码如下:
abstract class _Delegate<T> {
_DelegateState<T, _Delegate<T>> createState();
void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}
看到有个看上去跟状态相关的方法需要重写:createState()
,我们继续看一下_ValueInheritedProvider
重写的createState()
的实现:
@override
_ValueInheritedProviderState<T> createState() {
return _ValueInheritedProviderState<T>();
}
返回了_ValueInheritedProviderState
对象,_ValueInheritedProviderState
继承了_DelegateState
,而_DelegateState
持有了一个_InheritedProviderScopeElement
对象。继续看一下_ValueInheritedProviderState
的结构:
它定义了willUpdateDelegate()
和dispose()
这两个方法,用来做更新和注销。这么看来_ValueInheritedProviderState
这个类实际上是个状态的代理类,类似StatefulWidget
和State
的关系。我们一开始提到其实ChangeNotifierProvider
是个StatelessWidget
,那么它的状态肯定是由其他类代理的,由此可知,ChangeNotifierProvider
的状态是由_ValueInheritedProviderState
来代理。
ChangeNotifierProvider
对于Widget
的实现实际上是在父类InheritedProvider
进行的,我们看一下InheritedProvider
的结构:
终于看到了buildWithChild()
这个方法,这是真正我们想看的Widget
的内部结构的创建:
@override
Widget buildWithChild(BuildContext context, Widget? child) {
assert(
builder != null || child != null,
'$runtimeType used outside of MultiProvider must specify a child',
);
return _InheritedProviderScope<T>(
owner: this,
// ignore: no_runtimetype_tostring
debugType: kDebugMode ? '$runtimeType' : '',
child: builder != null
? Builder(
builder: (context) => builder!(context, child),
)
: child!,
);
}
我们看到我们所创建的builder
或child
实际上是被_InheritedProviderScope()
进行了包裹。我们继续分析_InheritedProviderScope
:
class _InheritedProviderScope<T> extends InheritedWidget {
const _InheritedProviderScope({
required this.owner,
required this.debugType,
required Widget child,
}) : super(child: child);
final InheritedProvider<T> owner;
final String debugType;
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
@override
_InheritedProviderScopeElement<T> createElement() {
return _InheritedProviderScopeElement<T>(this);
}
}
到这我们终于看到_InheritedProviderScope
继承了我们熟悉的InheritedWidget
,说明我们的创建的Widget
都是被InheritedWidget
进行了包裹。在createElement()
时返回了_InheritedProviderScopeElement
对象。_InheritedProviderScopeElement
继承InheritedElement
,并实现了InheritedContext
接口。我们先看一下它的结构:
首先我们关注到有个_delegateState
的变量,对应的就是我们上面所提到的_ValueInheritedProvider
,看一下它初始化的位置:
void performRebuild() {
if (_firstBuild) {
_firstBuild = false;
_delegateState = widget.owner._delegate.createState()
..element = this;
}
super.performRebuild();
}
在performRebuild
的时候,调用widget
的_delegate
对象的createState()
方法,即_ValueInheritedProvider
的createState()
方法,得到一个_ValueInheritedProviderState
对象。并将自己赋值给_ValueInheritedProviderState
的element
对象。 还记不记着在讲ListenableProvider
的时候提到它添加了listener
,这个listener
将会执行InheritedContext.markNeedsNotifyDependents()
方法,而markNeedsNotifyDependents()
的定义就在_InheritedProviderScope
里:
@override
void markNeedsNotifyDependents() {
if (!_isNotifyDependentsEnabled) {
return;
}
markNeedsBuild();
_shouldNotifyDependents = true;
}
这里我看看到它将_InheritedProviderScopeElement
标志为markNeedsBuild()
,即需要被rebuild
的组件,然后将_shouldNotifyDependents
标志为true
。
回到我们的ChangeNotifier
:当我们调用notifyListeners()
来通知数据变化的时候,如果有listener
被注册,实际上会执行InheritedContext.markNeedsNotifyDependents()
方法,具体会执行到的位置在ChangeNotifierProvider
组件的父类InheritedProvider
包裹的_InheritedProviderScope
这个InheritedWidget
对应的_InheritedProviderScopeElement
的markNeedsNotifyDependents()
方法。
整个过程可总结为下图:
不过到目前为止,我们只是知道了这个流程,但是listener
什么时候被注册,子组件又是如何被刷新的呢?我们继续从实例中的Provider.of<>()
分析起。
在取数据的时候,如实例代码:Provider.of<ProviderViewModel>(context).number;
,来看of()
方法的实现:
static T of<T>(BuildContext context, {bool listen = true}) {
assert(
context.owner!.debugBuilding ||
listen == false ||
debugIsInInheritedProviderUpdate,
);
final inheritedElement = _inheritedElementOf<T>(context);
if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return inheritedElement.value;
}
首先获取context
的_InheritedProviderScopeElement
对象,然后调用context.dependOnInheritedElement()
方法。这个方法我们很熟悉了,在上个章节介绍InheritedWidget
的时候了解过,作用是让子组件找到InheritedElement
类型的父组件,父组件调用setDependencies()
,为子组件向_dependents
中添加注册以形成依赖关系,InheritedWidget
组件更新时可以根据此列表通知子组件。接着调用inheritedElement.value
返回一个ChangeNotifier
对象。这个就是之前我们在ChangeNotifierProvider.value()
过程中传入的ChangeNotifier
对象。那么在取值的过程中还做了些什么呢?我们继续分析:
@override
T get value => _delegateState.value;
它实际上取的是_ValueInheritedProviderState
的value
:
@override
T get value {
element!._isNotifyDependentsEnabled = false;
_removeListener ??= delegate.startListening?.call(element!, delegate.value);
element!._isNotifyDependentsEnabled = true;
assert(delegate.startListening == null || _removeListener != null);
return delegate.value;
}
从这段代码中,我们看到了,在取值的过程中,调用了delegate.startListening?.call(element!, delegate.value)
,为上一节所提到的listener
进行了注册。意味着当notifyListeners()
时,这个listener
将会执行InheritedContext.markNeedsNotifyDependents()
方法。我们还记着在分析InheritedWidget
的时候说明父通知子的时候,会调用InheritedElement
的notifyDependent()
方法,那么在Provider
中,会在其子类_InheritedProviderScopeElement
进行实现,代码如下:
@override
void notifyDependent(InheritedWidget oldWidget, Element dependent) {
final dependencies = getDependencies(dependent);
if (kDebugMode) {
ProviderBinding.debugInstance.providerDidChange(_debugId);
}
var shouldNotify = false;
if (dependencies != null) {
if (dependencies is _Dependency<T>) {
if (dependent.dirty) {
return;
}
for (final updateShouldNotify in dependencies.selectors) {
try {
assert(() {
_debugIsSelecting = true;
return true;
}());
shouldNotify = updateShouldNotify(value);
} finally {
assert(() {
_debugIsSelecting = false;
return true;
}());
}
if (shouldNotify) {
break;
}
}
} else {
shouldNotify = true;
}
}
if (shouldNotify) {
dependent.didChangeDependencies();
}
}
先取shouldNotify
的值,由于我们没有用selector
,这时候shouldNotify
为true
,当前Widget
将会进行rebuild
。那么如果我们并没有用Consumer
,这时候的Provider.of<ProviderViewModel>(context)
的context
实际上是ChangeNotifierProvider
对应的context
,整个ChangeNotifierProvider
都会进行rebuild
操作。Consumer
的局部更新如何实现的呢?
其实这个很简单,看一下Consumer
的实现:
class Consumer<T> extends SingleChildStatelessWidget {
/// {@template provider.consumer.constructor}
/// Consumes a [Provider<T>]
/// {@endtemplate}
Consumer({
Key? key,
required this.builder,
Widget? child,
}) : super(key: key, child: child);
final Widget Function(
BuildContext context,
T value,
Widget? child,
) builder;
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
}
在buildWithChild()
中实际上也是使用了Provider.of<T>(context)
,不过要注意的是,这个context
是当前组件的context
,所以最终只有被Consumer
包裹住的子组件才会向_dependents
中添加注册以形成依赖关系,才会被标记为dirty
从而进行rebuild
。
好了到此为止,Provider
的解析已经完成了,总结一下:
Provider
实际上是个无状态的StatelessWidget
,通过包装了InheritedWidget
实现父子组件的数据共享,通过自定义InheritedElement
实现刷新。Provider
通过与ChangeNotifier
配合使用,实现了观察者模式,Provider
会将子组件添加到父组件的依赖关系中,在notifyListeners()
时,会执行InheritedContext.markNeedsNotifyDependents()
,将组件标记为dirty
等待重绘。Consumer
会只将被它包裹住的子组件注册给父的_dependents
形成依赖关系,从而实现了局部更新。下面我们看一下几种在Flutter
中比较流行的状态同步框架并进行比较。
这几个状态同步框架,包括其衍生的一些框架的核心原理都是利用了InheritedWidget
实现的。虽然Google
官方推荐的使用Provider
,但在开发过程中需要根据项目大小,开发人员习惯等因素去考虑。
本文对Provider
框架的使用及实现原理作详细的说明,为了能够更好的进行理解,也对InheritedWidget
的实现进行了详细的说明,并在最后对主流的状态管理框架进行比较。希望能帮助大家更好的理解Flutter
的数据共享机制,并根据自身需求选择合适的框架应用到实际项目中。
扫一扫
在手机上阅读