使用
Provider
的使用非常簡(jiǎn)單铲掐,通常使用ChangeNotifierProvider
配合ChangeNotifier
一起使用來(lái)實(shí)現(xiàn)狀態(tài)的管理與Widget
的更新豪硅。其中ChangeNotifier
是系統(tǒng)提供的,用來(lái)負(fù)責(zé)數(shù)據(jù)的變化通知和橙。ChangeNotifierProvider
本質(zhì)上其實(shí)就是Widget
仔燕,它作為父節(jié)點(diǎn)Widget
,可將數(shù)據(jù)共享給其所有子節(jié)點(diǎn)Widget
使用或更新魔招。具體的原理解析在后續(xù)章節(jié)會(huì)進(jìn)行說(shuō)明晰搀。所以通常我們只需要三步即可利用Provider
來(lái)實(shí)現(xiàn)狀態(tài)管理。
1.創(chuàng)建混合或繼承ChangeNotifier
的Model
办斑,用來(lái)實(shí)現(xiàn)數(shù)據(jù)更新的通知并監(jiān)聽(tīng)數(shù)據(jù)的變化外恕。
2.創(chuàng)建ChangeNotifierProvider
,用來(lái)聲明Provider
乡翅,實(shí)現(xiàn)跨組建的數(shù)據(jù)共享鳞疲。
3.接收共享數(shù)據(jù)。
我們來(lái)舉個(gè)例子蠕蚜,看看它是怎么在父子之間進(jìn)行數(shù)據(jù)共享的:
例1 Provider的使用:
- 創(chuàng)建
Model
class ProviderViewModel with ChangeNotifier {
int _number = 0;
get number => _number;
void addNumber() {
_number++;
notifyListeners();
}
}
上面的代碼很簡(jiǎn)單尚洽,調(diào)用addNumber()
方法讓_number
加1,并調(diào)用notifyListeners()
通知給監(jiān)聽(tīng)方靶累。
- 創(chuàng)建
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("我是父節(jié)點(diǎn)"),
Text(
"Parent number is: ${Provider.of<ProviderViewModel>(context).number}"),
ChildA(),
//ChildB(),
//ChildC()
],
);
},
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_providerViewModel.addNumber();
}, //使用context.read不會(huì)調(diào)用rebuild
),
);
}
}
我們用ChangeNotifierProvider
將父布局包裹腺毫,在父或子節(jié)點(diǎn)ChildA
通過(guò)Provider.of<T>(BuildContext context, {bool listen = true})
進(jìn)行數(shù)據(jù)操作,可同步更新父與子的數(shù)據(jù)與UI挣柬。其中listen
默認(rèn)為true
可監(jiān)聽(tīng)數(shù)據(jù)的變化潮酒,為false
的情況只可讀取數(shù)據(jù)的值。
- 接收共享數(shù)據(jù):
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();
})
],
),
);
}
}
來(lái)看一下效果:
我們可以看到不管是在父節(jié)點(diǎn)還是在子節(jié)點(diǎn)邪蛔,都可以對(duì)ProviderViewModel
的數(shù)據(jù)進(jìn)行操作和監(jiān)聽(tīng)急黎。例1在操作與讀取時(shí)使用的是Provider.of<T>(BuildContext context, {bool listen = true})
的方式,為了可以更明確對(duì)于Provider
的操作,我們可將它替換為context.watch<>
()和context.read<>()
方式勃教。 我們可以通過(guò)源碼看到淤击,context.watch<>()
和context.read<>()
方法其實(shí)都是調(diào)用Provider.of<T>(BuildContext context, {bool listen = true})
來(lái)實(shí)現(xiàn)的:
T watch<T>() {
return Provider.of<T>(this);
}
T read<T>() {
return Provider.of<T>(this, listen: false);
}
語(yǔ)義更加清晰明確。 如:
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("我是子節(jié)點(diǎn)"),
Text("Child B number: ${context.watch<ProviderViewModel>().number}"),
MaterialButton(
child: const Text("Add Number"),
color: Colors.white,
onPressed: () {
context.read<ProviderViewModel>().addNumber();
})
],
),
);
}
}
ChildB
與ChildA
實(shí)際上是一致的故源。我們把ProviderTestPage
的ChildB()
放開(kāi):
其中遭贸,每點(diǎn)擊一次父Widget
右下角的加號(hào)或子Widget
的Add Number按鈕,我們看一下Log
打印的結(jié)果:
flutter: childA build
flutter: child build
我們會(huì)發(fā)現(xiàn)每一次的操作心软,都會(huì)導(dǎo)致ChildA
與ChildB
整體重新build
。但實(shí)際上從代碼中我們可知著蛙,在ChildA
和ChildB
中删铃,只有以下的Text()
會(huì)監(jiān)聽(tīng)ProviderViewModel
的數(shù)據(jù)更新:
//ChildA:
Text("Child A number: ${Provider.of<ProviderViewModel>(context).number}")
//ChildB:
Text("Child B number: ${context.watch<ProviderViewModel>().number}")
那么我們希望可以實(shí)現(xiàn)局部的更新該如何實(shí)現(xiàn)?Flutter
提供了Consumer<>()
來(lái)進(jìn)行支持踏堡。下面我們來(lái)看一下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("我是子節(jié)點(diǎn)"),
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()
來(lái)監(jiān)聽(tīng)ProviderViewModel
的數(shù)據(jù)更新猎唁,我們用Consumer<>()
包裹住Text()
,其中builder
的傳參value
即是ProviderViewModel
對(duì)象顷蟆,把ProviderTestPage
的ChildC()
放開(kāi)诫隅,我們看一下結(jié)果:
再打印一下Log
:
flutter: child build
flutter: childA build
flutter: Child Consumer builder
從Log
中我們可以得知,ChildC
并沒(méi)有被rebuild
帐偎,而是由Consumer
調(diào)用內(nèi)部的builder
來(lái)實(shí)現(xiàn)局部更新的逐纬。 到此為止,一個(gè)簡(jiǎn)單的Provider
使用就介紹完成削樊。另外Provider
還提供了ProxyProvider
豁生,從名字上來(lái)看,我們可知這是個(gè)代理Provider
漫贞,它是用來(lái)協(xié)調(diào)Model
與Model
之間的更新甸箱,比如一個(gè)ModelA
依賴(lài)另一個(gè)ModelB
,ModelB
更新迅脐,他就要讓依賴(lài)它的ModelA
也隨之更新芍殖。我們直接上代碼來(lái)看一下它的用法,還是分三步:
例2 ProxyProvider的使用:
- 創(chuàng)建
ProxyProviderViewModel
:
class ProxyProviderViewModel with ChangeNotifier {
int number;
ProxyProviderViewModel(this.number);
String get title {
return "The number is: $number";
}
}
這個(gè)類(lèi)只是簡(jiǎn)單的在構(gòu)造方法例傳入int值谴蔑,并創(chuàng)建get方法得到一段文本豌骏。
- 創(chuàng)建
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();
})
],
);
},
),
);
}
}
我們?cè)?code>body中用MultiProvider
來(lái)包裹布局。MultiProvider
的作用是同時(shí)可聲明多個(gè)Provider
供使用树碱,為參數(shù)providers
添加Provider
數(shù)組肯适。我們首先聲明一個(gè)ChangeNotifierProvider
,同例1中的ProviderViewModel
成榜。接著我們聲明一個(gè)ChangeNotifierProxyProvider
用來(lái)做代理Provider
框舔。其中create
參數(shù)是ProxyProvider
的創(chuàng)建,update參數(shù)是ProxyProvider
的更新。在我們的例子中刘绣,實(shí)際上是對(duì)ProviderViewModel
進(jìn)行數(shù)據(jù)操作樱溉,由ProxyProviderViewModel
監(jiān)聽(tīng)ProviderViewModel
的數(shù)據(jù)變化。
- 接收共享數(shù)據(jù):
class ChildProxy extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: Column(
children: [
Text(context.watch<ProxyProviderViewModel>().title),
],
),
);
}
}
在ChildProxy
中纬凤,我們監(jiān)聽(tīng)ProxyProviderViewModel
的數(shù)據(jù)變化福贞,并將title顯示在Text()
中,進(jìn)行UI上的更新停士。 我們看一下效果:
我們調(diào)用context.read<ProviderViewModel>().addNumber()
對(duì)ProviderViewModel
的數(shù)據(jù)進(jìn)行更新挖帘,可同時(shí)更新ProxyProviderViewModel
的number
對(duì)象,而ChildProxy
由于監(jiān)聽(tīng)了ProxyProviderViewModel
的數(shù)據(jù)變化恋技,會(huì)因此更新UI中title
的內(nèi)容拇舀。
好了,到此為止蜻底,Provider
的使用介紹到這里骄崩,下面我們將針對(duì)Provider
的原理進(jìn)行說(shuō)明。Provider
實(shí)際上是對(duì)InheritedWidget
進(jìn)行了封裝薄辅,它才是真正實(shí)現(xiàn)父與子數(shù)據(jù)共享的重要元素要拂,所以為了理清Provider
的原理,我們必須先弄清楚InheritedWidget
的實(shí)現(xiàn)過(guò)程站楚。
InheritedWidget的原理及解析
InheritedWidget
提供了沿樹(shù)向下脱惰,共享數(shù)據(jù)的功能,系統(tǒng)中的Provider
窿春,Theme
等實(shí)現(xiàn)都是依賴(lài)于它枪芒。弄清楚它的原理,對(duì)于理解Flutter
的數(shù)據(jù)共享方式會(huì)有很大的幫助谁尸。本章節(jié)將先通過(guò)實(shí)例說(shuō)明InheritedWidget
的用法舅踪,然后進(jìn)行原理的解析。
使用
我們舉個(gè)簡(jiǎn)單的例子:
這是一個(gè)簡(jiǎn)單的樹(shù)結(jié)構(gòu)良蛮,其中ChildA
和ChildC
需要共享Data
這個(gè)數(shù)據(jù)抽碌,ChildB
不需要。傳遞方式有很多種决瞳,比如說(shuō)通過(guò)構(gòu)造方法傳遞货徙,通過(guò)函數(shù)調(diào)用,函數(shù)回調(diào)傳遞等皮胡。但是如果樹(shù)層級(jí)非常多的話(huà)痴颊,剛才提到的傳遞方式將會(huì)對(duì)整個(gè)代碼結(jié)構(gòu)帶來(lái)災(zāi)難,包括代碼耦合度過(guò)高屡贺,回調(diào)地獄等蠢棱。我們看看InheritedWidget
是怎么處理的:
1.作為整個(gè)樹(shù)的父節(jié)點(diǎn)锌杀,需要使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()
方法需要被重寫(xiě),用來(lái)判斷現(xiàn)有共享數(shù)據(jù)和舊的共享數(shù)據(jù)是否一致泻仙,是否需要傳遞給已注冊(cè)的子組件糕再。 -
of()
方法是一種約定俗成的通用寫(xiě)法,只是起到方便調(diào)用的作用玉转。其中context.dependOnInheritedWidgetOfExactType()
是為它的子組件注冊(cè)了依賴(lài)關(guān)系突想。 通過(guò)這樣的方式,將ChildA
聲明成了一個(gè)給子組件共享數(shù)據(jù)的Widget
究抓。
ChildB
就是一個(gè)中間層級(jí)的普通Widget
猾担,用來(lái)連接樹(shù)結(jié)構(gòu):
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("我是子節(jié)點(diǎn) ChildB"), widget.child],
),
);
}
}
ChildC
依賴(lài)ChildA
,并讀取ChildA
的共享數(shù)據(jù)刺下,代碼如下:
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("我是子節(jié)點(diǎn) ChildC"),
Text("Child C number: ${ChildA.of(context)?.number}"),
],
),
);
}
}
ChildC
通過(guò)ChildA.of(context)?.number
的方式讀取ChildA
的共享數(shù)據(jù)垒探。 我們把這個(gè)樹(shù)串起來(lái):
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++;
});
},
),
);
}
}
在點(diǎn)擊floatingActionButton
的時(shí)候,修改_number
的值怠李,通過(guò)構(gòu)造方法傳遞給ChildA
,整個(gè)樹(shù)重新build
蛤克。ChildC
讀取ChildA
的數(shù)據(jù)捺癞,并進(jìn)行UI的更新,我們看一下結(jié)果:
ChildC
接收到了數(shù)據(jù)變化并進(jìn)行了更新构挤,我們?cè)賮?lái)看一下Log
:
flutter: ChildB build
flutter: Child didChangeDependencies
flutter: Child build
在這個(gè)過(guò)程中髓介,會(huì)發(fā)現(xiàn)ChildB
和ChildC
都進(jìn)行了rebuild
,由于ChildC
依賴(lài)ChildA
的共享數(shù)據(jù)筋现,ChildC
在rebuild
之前執(zhí)行了didChangeDependencies()
方法唐础,說(shuō)明ChildC
的依賴(lài)關(guān)系發(fā)生了改變;而ChildB
由于不依賴(lài)ChildA
的共享數(shù)據(jù)所以并沒(méi)有執(zhí)行didChangeDependencies()
矾飞。
這個(gè)例子給出了InheritedWidget
的一個(gè)基本使用方式一膨,但需要注意的是,在整個(gè)樹(shù)結(jié)構(gòu)中洒沦,其實(shí)ChildB
是不依賴(lài)ChildA
的共享數(shù)據(jù)的豹绪,按理來(lái)說(shuō),在數(shù)據(jù)發(fā)生變化申眼,我們是不希望ChildB
進(jìn)行rebuild
的瞒津。所以需要說(shuō)明的是,InheritedWidget
的正確用法并不是通過(guò)setState()
來(lái)實(shí)現(xiàn)rebuild
的括尸,這里用setState()
舉例僅僅是為了將整個(gè)流程串起來(lái)巷蚪。這個(gè)例子的重點(diǎn)在于,依賴(lài)父組件的共享數(shù)據(jù)的子組件濒翻,將在生命周期中執(zhí)行didChangeDependencies()
方法屁柏。我們可以通過(guò)ValueNotifier+ValueListenable
來(lái)進(jìn)行局部的更新啦膜,這部分出離了本文的內(nèi)容,先不作展開(kāi)前联。
接下來(lái)我們分析一下InheritedWidget
是如何實(shí)現(xiàn)父與子之間的數(shù)據(jù)共享的功戚。
原理及解析
為了實(shí)現(xiàn)父與子的數(shù)據(jù)共享,我們需要弄清楚兩件事:
- 父綁定子的方式
- 父通知子的方式
父綁定子的方式
我們先來(lái)看一下InheritedWidget
這個(gè)類(lèi):
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
啸臀。它只有兩個(gè)方法,一個(gè)是updateShouldNotify()
烁落,在上面的例子中可知是用來(lái)判斷現(xiàn)有共享數(shù)據(jù)和舊的共享數(shù)據(jù)是否一致乘粒,是否需要傳遞給已注冊(cè)的子組件的。另外還重寫(xiě)了createElement()
方法伤塌,創(chuàng)建一個(gè)InheritedElement
對(duì)象灯萍。InheritedElement
最終繼承Element
,我們先看一下它的結(jié)構(gòu):
從命名中我們就可知setDependencies()
是用來(lái)綁定依賴(lài)關(guān)系的每聪。接下來(lái)我們從子組件獲取InheritedWidget
實(shí)例開(kāi)始看起旦棉,看看具體的綁定流程。如實(shí)例中的如下代碼:
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;
}
真正的實(shí)現(xiàn)是在Element
中進(jìn)行的药薯。其中_inheritedWidgets
是個(gè)Map
绑洛,key
為T
的類(lèi)型。從上面代碼我們可以知道童本,先從_inheritedWidgets
里尋找類(lèi)型為T
的InheritedElement
真屯,即父的InheritedElement
。_updateInheritance()
是在mount()
和activate()
調(diào)用的穷娱,_inheritedWidgets
的初始化在子類(lèi)InheritedElement
的_updateInheritance()
中的實(shí)現(xiàn)如下:
//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
中的實(shí)現(xiàn)如下:
//Element
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
從上面的代碼我們可以得知绑蔫,普通Element
組件在生命周期的初始階段,它的_inheritedWidgets
為父組件的_inheritedWidgets
泵额。而_inheritedWidgets
的InheritedElement
配深,會(huì)將自己添加到_inheritedWidgets
中,從而通過(guò)此方式將組件和InheritedWidgets
的依賴(lài)關(guān)系層層向下傳遞嫁盲,每一個(gè)Element
中都含有_inheritedWidgets
集合凉馆,此集合中包含了此組件的父組件且是InheritedWidgets
組件的引用關(guān)系。接下來(lái)我們回到dependOnInheritedWidgetOfExactType()
方法亡资,ancestor
已經(jīng)被加到_inheritedWidgets
中澜共,所以它不為空,我們繼續(xù)看里面dependOnInheritedElement()
的實(shí)現(xiàn):
//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;
}
好到此為止锥腻,我們證實(shí)了之前的猜測(cè)嗦董,子組件找到InheritedElement
類(lèi)型的父組件,父組件調(diào)用setDependencies()
瘦黑,為子組件向_dependents
中添加注冊(cè)京革,InheritedWidget
組件更新時(shí)可以根據(jù)此列表通知子組件奇唤。將以上過(guò)程總結(jié)一下,如下圖:
- 父組件在
InheritedElement
的初始階段:mount()
和activate()
的時(shí)候調(diào)用_updateInheritance()
方法將自己添加到_inheritedWidgets
中匹摇。其他Element
子組件會(huì)直接拿父組件的_inheritedWidgets
咬扇。 - 子組件在調(diào)用
context.dependOnInheritedWidgetOfExactType<>()
時(shí),將自己注冊(cè)給_inheritedWidgets
中獲取的InheritedElement
類(lèi)型的父組件的dependents
中廊勃,從而實(shí)現(xiàn)了依賴(lài)關(guān)系的確定懈贺。
接下來(lái)我們看一下當(dāng)組件發(fā)生變化時(shí),父通知子的方式坡垫。
父通知子的方式
在實(shí)例中梭灿,當(dāng)setState()
發(fā)生數(shù)據(jù)改變的時(shí)候,經(jīng)過(guò)一系列處理后冰悠,會(huì)走到InheritedElement的updated()
方法中去:
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
當(dāng)執(zhí)行了我們自定義InheritedWidget
的updateShouldNotify()
判斷現(xiàn)有共享數(shù)據(jù)和舊的共享數(shù)據(jù)是否一致需要更新后堡妒,繼續(xù)執(zhí)行父類(lèi)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()
中會(huì)對(duì)_dependents
的key
進(jìn)行遍歷溉卓,然后執(zhí)行notifyDependent()
進(jìn)行通知皮迟。接著我們看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();
}
它調(diào)用了每個(gè)dependent
的didChangeDependencies()
方法,來(lái)通知InheritedWidget
依賴(lài)發(fā)生了變化桑寨,當(dāng)前element
需要被標(biāo)記為dirty
伏尼,重新進(jìn)行build
。到此為止西疤,完成了當(dāng)數(shù)據(jù)發(fā)生變化時(shí),父通知子的流程休溶。我們看一下父通知子的流程圖:
總結(jié)一下就是當(dāng)InheritedElement
數(shù)據(jù)發(fā)生變化而更新的時(shí)候代赁,父InheritedWidget
會(huì)遍歷_dependents
,子會(huì)執(zhí)行didChangeDependencies()
方法將子組件標(biāo)記為dirty
而重新build
兽掰。
了解了InheritedWidget
的實(shí)現(xiàn)后芭碍,我們下個(gè)章節(jié)對(duì)Provider
進(jìn)行解析。
Provider解析
接下來(lái)我們分析一下Provider
的實(shí)現(xiàn)孽尽。還記著文章開(kāi)頭窖壕,我們說(shuō)明需要三步來(lái)利用Provider
來(lái)實(shí)現(xiàn)狀態(tài)管理。
1.創(chuàng)建混合或繼承ChangeNotifier
的Model
杉女,用來(lái)實(shí)現(xiàn)數(shù)據(jù)更新的通知并監(jiān)聽(tīng)數(shù)據(jù)的變化瞻讽。
2.創(chuàng)建ChangeNotifierProvider
,用來(lái)聲明Provider
熏挎,實(shí)現(xiàn)跨組建的數(shù)據(jù)共享速勇。
3.接收共享數(shù)據(jù)。
我們從創(chuàng)建Model開(kāi)始講起:
ChangeNotifier
ChangeNotifier
實(shí)現(xiàn)了Listenable
接口坎拐,而Listenable
實(shí)際上就是一個(gè)觀(guān)察者模型烦磁。我們先來(lái)看一下ChangeNotifier
的結(jié)構(gòu):
在ChangeNotifier
里維護(hù)了一個(gè)_listeners
對(duì)象养匈,通過(guò)addListener()
和removeListener()
進(jìn)行添加或刪除。在調(diào)用notifyListeners()
的時(shí)候?qū)?shù)據(jù)通知給_listeners
都伪。ChangeNotifier
非常簡(jiǎn)單呕乎,我們接著來(lái)分析ChangeNotifierProvider
的實(shí)現(xiàn)。
ChangeNotifierProvider
ChangeNotifierProvider
繼承了多個(gè)層級(jí):ListenableProvider->InheritedProvider->SingleChildStatelessWidget->StatelessWidget
陨晶,實(shí)際上它是個(gè)StatelessWidget
猬仁。我們從ChangeNotifierProvider.value()
方法開(kāi)始:
//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
對(duì)象,我們繼續(xù)看super()
的調(diào)用:
//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
創(chuàng)建了一個(gè)VoidCallback
對(duì)象珍逸,其中value
是個(gè)Listenable
對(duì)象逐虚,就是我們傳入的ChangeNotifier
對(duì)象。它的實(shí)現(xiàn)是為ChangeNotifier
添加listener
谆膳,這個(gè)listener
將會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()
方法钳枕,這個(gè)我們之后再做討論拴疤。總而言之,ListenableProvider
的作用就是幫我們?yōu)?code>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
這一層,我們發(fā)現(xiàn)builder
沒(méi)有被繼續(xù)傳下去了法希,InheritedProvider
持有了一個(gè)_ValueInheritedProvider
類(lèi)型的_delegate
悠反。它的父類(lèi)_Delegate
的代碼如下:
abstract class _Delegate<T> {
_DelegateState<T, _Delegate<T>> createState();
void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}
看到有個(gè)看上去跟狀態(tài)相關(guān)的方法需要重寫(xiě):createState()
,我們繼續(xù)看一下_ValueInheritedProvider
重寫(xiě)的createState()
的實(shí)現(xiàn):
@override
_ValueInheritedProviderState<T> createState() {
return _ValueInheritedProviderState<T>();
}
返回了_ValueInheritedProviderState
對(duì)象注盈,_ValueInheritedProviderState
繼承了_DelegateState
晃危,而_DelegateState
持有了一個(gè)_InheritedProviderScopeElement
對(duì)象。繼續(xù)看一下_ValueInheritedProviderState
的結(jié)構(gòu):
它定義了willUpdateDelegate()
和dispose()
這兩個(gè)方法老客,用來(lái)做更新和注銷(xiāo)僚饭。這么看來(lái)_ValueInheritedProviderState
這個(gè)類(lèi)實(shí)際上是個(gè)狀態(tài)的代理類(lèi),類(lèi)似StatefulWidget
和State
的關(guān)系胧砰。我們一開(kāi)始提到其實(shí)ChangeNotifierProvider
是個(gè)StatelessWidget
鳍鸵,那么它的狀態(tài)肯定是由其他類(lèi)代理的,由此可知尉间,ChangeNotifierProvider
的狀態(tài)是由_ValueInheritedProviderState
來(lái)代理偿乖。
ChangeNotifierProvider
對(duì)于Widget
的實(shí)現(xiàn)實(shí)際上是在父類(lèi)InheritedProvider
進(jìn)行的,我們看一下InheritedProvider
的結(jié)構(gòu):
終于看到了buildWithChild()
這個(gè)方法哲嘲,這是真正我們想看的Widget
的內(nèi)部結(jié)構(gòu)的創(chuàng)建:
@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!,
);
}
我們看到我們所創(chuàng)建的builder
或child
實(shí)際上是被_InheritedProviderScope()
進(jìn)行了包裹贪薪。我們繼續(xù)分析_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
,說(shuō)明我們的創(chuàng)建的Widget
都是被InheritedWidget
進(jìn)行了包裹眠副。在createElement()
時(shí)返回了_InheritedProviderScopeElement
對(duì)象古掏。_InheritedProviderScopeElement
繼承InheritedElement
,并實(shí)現(xiàn)了InheritedContext
接口侦啸。我們先看一下它的結(jié)構(gòu):
首先我們關(guān)注到有個(gè)_delegateState
的變量丧枪,對(duì)應(yīng)的就是我們上面所提到的_ValueInheritedProvider
,看一下它初始化的位置:
void performRebuild() {
if (_firstBuild) {
_firstBuild = false;
_delegateState = widget.owner._delegate.createState()
..element = this;
}
super.performRebuild();
}
在performRebuild
的時(shí)候私恬,調(diào)用widget
的_delegate
對(duì)象的createState()
方法债沮,即_ValueInheritedProvider的createState()
方法,得到一個(gè)_ValueInheritedProviderState
對(duì)象本鸣。并將自己賦值給_ValueInheritedProviderState
的element
對(duì)象疫衩。 還記不記著在講ListenableProvider
的時(shí)候提到它添加了listener
,這個(gè)listener
將會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()
方法荣德,而markNeedsNotifyDependents()
的定義就在_InheritedProviderScope
里:
@override
void markNeedsNotifyDependents() {
if (!_isNotifyDependentsEnabled) {
return;
}
markNeedsBuild();
_shouldNotifyDependents = true;
}
這里我看看到它將_InheritedProviderScopeElement
標(biāo)志為markNeedsBuild()
闷煤,即需要被rebuild的組件,然后將_shouldNotifyDependents
標(biāo)志為true
涮瞻。
回到我們的ChangeNotifier
:當(dāng)我們調(diào)用notifyListeners()
來(lái)通知數(shù)據(jù)變化的時(shí)候鲤拿,如果有listener
被注冊(cè),實(shí)際上會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()
方法署咽,具體會(huì)執(zhí)行到的位置在ChangeNotifierProvider
組件的父類(lèi)InheritedProvider
包裹的_InheritedProviderScope
這個(gè)InheritedWidget
對(duì)應(yīng)的_InheritedProviderScopeElement
的markNeedsNotifyDependents()
方法近顷。
整個(gè)過(guò)程可總結(jié)為下圖:
不過(guò)到目前為止,我們只是知道了這個(gè)流程宁否,但是listener
什么時(shí)候被注冊(cè)窒升,子組件又是如何被刷新的呢?我們繼續(xù)從實(shí)例中的Provider.of<>()
分析起家淤。
Provider.of<>()
在取數(shù)據(jù)的時(shí)候异剥,如實(shí)例代碼:Provider.of<ProviderViewModel>(context).number
;瑟由,來(lái)看of()
方法的實(shí)現(xiàn):
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
對(duì)象絮重,然后調(diào)用context.dependOnInheritedElement()
方法。這個(gè)方法我們很熟悉了歹苦,在上個(gè)章節(jié)介紹InheritedWidget
的時(shí)候了解過(guò)青伤,作用是讓子組件找到InheritedElement
類(lèi)型的父組件,父組件調(diào)用setDependencies()
殴瘦,為子組件向_dependents
中添加注冊(cè)以形成依賴(lài)關(guān)系狠角,InheritedWidget
組件更新時(shí)可以根據(jù)此列表通知子組件。接著調(diào)用inheritedElement.value
返回一個(gè)ChangeNotifier
對(duì)象蚪腋。這個(gè)就是之前我們?cè)?code>ChangeNotifierProvider.value()過(guò)程中傳入的ChangeNotifier
對(duì)象丰歌。那么在取值的過(guò)程中還做了些什么呢姨蟋?我們繼續(xù)分析:
@override
T get value => _delegateState.value;
它實(shí)際上取的是_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;
}
從這段代碼中,我們看到了立帖,在取值的過(guò)程中眼溶,調(diào)用了delegate.startListening?.call(element!, delegate.value)
,為上一節(jié)所提到的listener
進(jìn)行了注冊(cè)晓勇。意味著當(dāng)notifyListeners()
時(shí)堂飞,這個(gè)listener
將會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()
方法。我們還記著在分析InheritedWidget
的時(shí)候說(shuō)明父通知子的時(shí)候绑咱,會(huì)調(diào)用InheritedElement的notifyDependent()
方法绰筛,那么在Provider
中,會(huì)在其子類(lèi)_InheritedProviderScopeElement
進(jìn)行實(shí)現(xiàn)描融,代碼如下:
@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
的值铝噩,由于我們沒(méi)有用selector
,這時(shí)候shouldNotify
為true
稼稿,當(dāng)前Widget
將會(huì)進(jìn)行rebuild
薄榛。那么如果我們并沒(méi)有用Consumer
,這時(shí)候的Provider.of<ProviderViewModel>(context)
的context
實(shí)際上是ChangeNotifierProvider
對(duì)應(yīng)的context
让歼,整個(gè)ChangeNotifierProvider
都會(huì)進(jìn)行rebuild
操作敞恋。Consumer
的局部更新如何實(shí)現(xiàn)的呢?
Consumer
其實(shí)這個(gè)很簡(jiǎn)單谋右,看一下Consumer
的實(shí)現(xiàn):
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()
中實(shí)際上也是使用了Provider.of<T>(context)
硬猫,不過(guò)要注意的是,這個(gè)context
是當(dāng)前組件的context
改执,所以最終只有被Consumer
包裹住的子組件才會(huì)向_dependents
中添加注冊(cè)以形成依賴(lài)關(guān)系啸蜜,才會(huì)被標(biāo)記為dirty
從而進(jìn)行rebuild
。
好了到此為止辈挂,Provider
的解析已經(jīng)完成了衬横,總結(jié)一下:
-
Provider
實(shí)際上是個(gè)無(wú)狀態(tài)的StatelessWidget
,通過(guò)包裝了InheritedWidget
實(shí)現(xiàn)父子組件的數(shù)據(jù)共享终蒂,通過(guò)自定義InheritedElement
實(shí)現(xiàn)刷新蜂林。 -
Provider
通過(guò)與ChangeNotifier
配合使用,實(shí)現(xiàn)了觀(guān)察者模式拇泣,Provider
會(huì)將子組件添加到父組件的依賴(lài)關(guān)系中噪叙,在notifyListeners()
時(shí),會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()
霉翔,將組件標(biāo)記為dirty
等待重繪睁蕾。 -
Consumer
會(huì)只將被它包裹住的子組件注冊(cè)給父的_dependents
形成依賴(lài)關(guān)系,從而實(shí)現(xiàn)了局部更新。
下面我們看一下幾種在Flutter
中比較流行的狀態(tài)同步框架并進(jìn)行比較子眶。
幾種狀態(tài)同步框架的對(duì)比和選擇
這幾個(gè)狀態(tài)同步框架瀑凝,包括其衍生的一些框架的核心原理都是利用了InheritedWidget
實(shí)現(xiàn)的。雖然Google官方推薦的使用Provider
臭杰,但在開(kāi)發(fā)過(guò)程中需要根據(jù)項(xiàng)目大小猜丹,開(kāi)發(fā)人員習(xí)慣等因素去考慮。
轉(zhuǎn)載自(https://mp.weixin.qq.com/s/vUhDvHaStrTwbE4tovOqtA)硅卢。