目前,開發的flutter
項目中,狀態管理庫使用是Provider
,Provider
基於 InheritedWidget
組件封裝,想要減小平常開發採坑,就不得不去了解 InheritedWidget
組件的工做原理。因爲要從源碼角度分析 InheritedWidget
組件的工做原理,在閱讀本文前,最好對 flutter
的知識有必定了解,這樣才能更好的瞭解,本文所要表達的意思。android
flutter
基本使用。provider
的框架。Widget
和 Element
之間關係。Element
在 flutter
渲染時方法的調用。本文中用到的生產環境安全
/// 有效地沿樹向下傳播信息的 Widget 的基類,子 Widget 要想獲取最近特定類型的 InheritedWidget實例,請使用
/// [BuildContext.dependOnInheritedWidgetOfExactType]。子 Widget 以這種方式引用時,當 InheritedWidget 改變狀態
/// 時,會從新構建依賴的子 Widget。
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
複製代碼
InheritedWidget
代碼很簡單,主要有兩個方法:markdown
createElement
生成 InheritedElement
對象。updateShouldNotify
用於控制當前 InheritedWidget
發生變化, 所依賴的 Widget
是否須要重建。從 InheritedWidget
描述可得知,若是子 Widget
須要獲取 InheritedWidget
對象,能夠經過 BuildContext.dependOnInheritedWidgetOfExactType
獲取。看下 BuildContext
類的 dependOnInheritedWidgetOfExactType
。app
abstract class BuildContext {
/// 獲取給定類型「T」的最近 widget,它必須是具體 [InheritedWidget] 子類的類型,並將此構建上下文註冊到該 widget,以便當該 widget 更改時(或引入該類型的新 widget, 或 widget 消失),此構建上下文將被重建,以便它能夠從該 widget 獲取新值。
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object aspect });
/// 獲取與給定類型「T」的最近 widget 對應的 element,該 element 必須是具體 [InheritedWidget] 子類的類型。 若是找不到這樣的 element,則返回 null。
/// 調用這個方法是 O(1) 一個小的常數因子。 此方法不會像 [dependOnInheritedWidgetOfExactType] 那樣與目標創建關
/// 系。 不該從 [State.dispose] 調用此方法,由於此時 element 樹再也不穩定。 要從該方法引用祖先,請經過在
/// [State.didChangeDependencies] 中調用 [dependOnInheritedWidgetOfExactType] 來保存對祖先的引用。 使用
/// [State.deactivate] 中的此方法是安全的,每當 widget 從樹中移除時都會調用該方法。
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();
}
複製代碼
接下來,咱們寫一個基於 InheritedWidget
組件實現的 計數器
。框架
class CountScope extends InheritedWidget {
const CountScope({this.count, Widget child}) : super(child: child);
final int count;
static CountScope of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CountScope>();
}
@override
bool updateShouldNotify(CountScope oldWidget) {
return oldWidget.count != count;
}
}
複製代碼
CountScope
繼承 InheritedWidget
less
of
,子 Widget
獲取 CountScope
對象。[見1.1小節]
updateShouldNotify
當 oldWidget.count != count
刷新依賴的 widget
。[見1.1小節]
class CountWidget extends StatefulWidget {
const CountWidget({Key key}) : super(key: key);
@override
_CountWidgetState createState() => _CountWidgetState();
}
class _CountWidgetState extends State<CountWidget> {
@override
Widget build(BuildContext context) {
print('_CountWidgetState build');
final int count = CountScope.watch(context).count;
return Container(child: Text('$count', style: Theme.of(context).textTheme.headline4));
}
}
複製代碼
CountWidget
調用 CountScope.of(context).count
顯示計數結果。ide
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
print('_MyHomePageState build');
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
CountScope(count: _counter, child: CountWidget()),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
複製代碼
到這裏計數器功能差很少就完成了,當咱們點擊浮動 +
按鈕時,計數器的數值會累加,最後能夠看到效果就是這樣。函數
咱們在 1.1 小節
提過, updateShouldNotify
返回 true
,表示更新依賴的 Widget
,false
不更新,如今讓咱們驗證下。修改 CountScope
代碼,當 count < 3
,才能更新依賴的 widget
。性能
class CountScope extends InheritedWidget {
const CountScope({this.count, Widget child}) : super(child: child);
final int count;
static CountScope watch(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CountScope>();
}
@override
bool updateShouldNotify(CountScope oldWidget) {
//return oldWidget.count != count;
print('count:$count');
return count < 3;
}
}
複製代碼
結果確實如咱們所料那樣,不停的點擊 +
按鈕,界面上的數字一直顯示的是 2
。認真觀察 log
會發現,實際上 count
是會一直累加。優化
講到這裏,咱們就會有不少疑問?爲何 CountWidget
能獲取到 CountScope
中 count
的值?又爲何CountScope
中 count
數值有變化,可是當 count > 3
時,CountWidget
界面卻沒有更新呢?
遇事不決,看源碼
咱們知道調用 setState
後,把當前 element
標記爲 dirty
, 當下一次 vsync
信號到來的時候,回調執行 handleBeginFrame
和 handleDrawFrame
,而後通過一些列的調用,最後會調用 BuildOwner.buildScope
,遍歷 _dirtyElements
集合,調用 Element
的 rebuild
刷新組件。
本文不會完整介紹
Widget
的更新機制,有興趣的同窗,能夠本身去了解下。(接下來源碼片斷,都只保留關鍵代碼)。
前面,咱們提到調用 setState
後 ,通過一系列的調用,最終調用 Element
的 rebuild
。
abstract class Element extends DiagnosticableTree implements BuildContext {
/// 當調用 [BuildOwner.scheduleBuildFor] 會將該 Element 標記爲 dirty,
/// 當 Element 首次 build 時會被 [mount] 調用,當 widget 更改時由 [update] 調用。
void rebuild() {
if (!_active || !_dirty)
return;
performRebuild();
}
/// 在進行適當的檢查後由 rebuild() 調用。
@protected
void performRebuild(); /// [見2.4小節]
}
複製代碼
abstract class ComponentElement extends Element {
/// 調用 StatelessWidget 對象的 StatelessWidget.build 方法(對於無狀態小部件)
/// 或 State 對象的 State.build 方法(對於有狀態小部件),而後更新 widget 樹。
/// 在 mount 期間自動調用以生成第一個構建,並在 element 須要更新時經過 rebuild 調用。
@override
void performRebuild() {
Widget built;
try {
built = build();
} finally {
/// ...
_dirty = false;
}
try {
_child = updateChild(_child, built, slot); /// [見2.5小節]
} catch (e, stack) {
// ...
_child = updateChild(null, built, slot);
}
/// 子類應該覆蓋這個函數來爲它們的小部件實際調用適當的 `build` 函數
///(例如,[StatelessWidget.build] 或 [State.build])。
@protected
Widget build();
}
複製代碼
abstract class Element extends DiagnosticableTree implements BuildContext {
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget); /// [見2.6小節]
newChild = child;
} else {
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot); /// [見2.5.1小節]
}
return newChild;
}
}
複製代碼
若是第一次調用 updateChild
,默認 child = null
,就會執行 inflateWidget
,生成 Element newChild
對象,最後把 newChild
賦值給 _child
[2.4小節]
,後續就使用 newChild
傳入 updateChild
。
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
if (key is GlobalKey) {
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
// ...
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild;
}
}
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);// [見2.5.2小節]
return newChild;
}
複製代碼
調用 widget.createElement
,生成 Element
對象,而後調用 Element
的 mount
,遞歸調用,完成全部子 widget
的刷新。
void mount(Element parent, dynamic newSlot) {
//...
final Key key = widget.key;
if (key is GlobalKey) {
key._register(this);
}
_updateInheritance();
}
複製代碼
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
複製代碼
將父類中的 _inheritedWidgets
集合對象,傳到子類。
[2.5.1]
[2.5.2]
兩個小節,描述第一次加載 widget
過程。接下來介紹 child.update(newWidget)
。
@mustCallSuper
void update(covariant Widget newWidget) { /// [見2.7小節]
_widget = newWidget;
}
複製代碼
經過查看 [2.1]
類圖,發現最下層的子類是 InheritedElement
。
class InheritedElement extends ProxyElement {
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget); /// [見2.8小節]
}
}
複製代碼
widget.updateShouldNotify(oldWidget)
這個方法是否是似曾相識,這個就是一開始,咱們說的 InheritedWidget.updateShouldNotify
[見1.2.4小節]
,這裏也驗證以前說法,若是 true
表示會繼續後續的,false
不執行後續的操做。咱們接着看 super.updated
後面的流程。
經過查看 [2.1]
類圖, InheritedElement
繼承自 ProxyElement
。
abstract class ProxyElement extends ComponentElement {
@override
ProxyWidget get widget => super.widget as ProxyWidget;
@override
Widget build() => widget.child;
/// ...
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
/// ...
updated(oldWidget);
_dirty = true;
rebuild();
}
}
複製代碼
/// 當 widget 更改時, 在 build 期間調用。
/// 默認狀況下,調用 notifyClients。 子類能夠覆蓋此方法以免沒必要要地調用 notifyClients(若是舊的和新的小部件是等效的)。
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
複製代碼
/// 通知其餘對象與此 Elment 關聯的 Widget 已更改。
/// 在更改與此元素關聯的 Widget 以後但在重建此元素以前,在update期間(經過updated )調用
@protected
void notifyClients(covariant ProxyWidget oldWidget); // [見2.9小節]
複製代碼
class InheritedElement extends ProxyElement {
final Map<Element, Object> _dependents = HashMap<Element, Object>();
/// [2.5.2小節] 初始化時調用
@override
void _updateInheritance() {
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;
}
/// 返回使用 [setDependencies]爲 [dependent] 記錄的依賴項值。
/// 每一個依賴元素都映射到單個對象值,該值表示元素如何依賴此 [InheritedElement]。
/// 默認狀況下,此值爲 null,而且默認狀況下會無條件重建相關元素。
/// 子類可使用 [updateDependencies] 管理這些值
/// 以便他們能夠選擇性地重建 [notifyDependent] 中的依賴項。
/// 此方法一般僅在 [updateDependencies] 的覆蓋中調用。
/// 也能夠看看:
/// [updateDependencies],每次使用 [dependOnInheritedWidgetOfExactType] 建立依賴項時都會調用它。
/// [setDependencies],設置依賴元素的依賴值。
/// [notifyDependent],能夠覆蓋它以使用依賴項的依賴項值來決定是否須要重建依賴項。
/// [InheritedModel],這是一個使用該方法管理依賴值的類的例子。
@protected
Object getDependencies(Element dependent) {
return _dependents[dependent];
}
/// 爲dependent設置getDependencies值返回的值。
/// 每一個依賴元素都映射到單個對象值,該值表示元素如何依賴此InheritedElement 。
/// [updateDependencies]方法在默認狀況下將該值設置爲null,以便無條件地重建依賴元素。
/// 子類可使用[updateDependencies]管理這些值,以便它們能夠有選擇地在[NotifyDependency]中重建依賴項。
/// 此方法一般僅在updateDependencies覆蓋中調用。
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
/// 添加新的 [dependent] 時由 [dependOnInheritedWidgetOfExactType] 調用。
/// 每一個依賴元素均可以映射到單個對象值 [setDependencies]。 此方法可使用 [getDependencies] 查找現有依賴項。
/// 默認狀況下,此方法將 [dependent] 的繼承依賴項設置爲 null。 這僅用於記錄對 [dependent] 的無條件依賴。
/// 子類能夠管理本身的依賴項值,以便它們能夠在 [notifyDependent] 中重建依賴項。
/// [getDependencies],返回依賴項的當前值元素。
/// [setDependencies],設置依賴元素的值。
/// [notifyDependent],能夠覆蓋它以使用依賴的依賴值來決定是否須要重建依賴。
/// [InheritedModel],這是一個使用該方法的類的例子管理依賴值。
@protected
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
/// 由 [notifyClients] 爲每一個依賴調用。
/// 默認狀況下調用 [dependent.didChangeDependencies()] 。
/// 子類能夠覆蓋此方法以根據 [getDependencies] 的值有選擇地調用 [didChangeDependencies] 。
/// 也能夠看看:
/// updateDependencies ,每次使用 [dependOnInheritedWidgetOfExactType] 建立依賴項時都會調用它。
/// getDependencies ,它返回依賴元素的當前值。
/// setDependencies ,它設置依賴元素的值。
/// InheritedModel ,這是使用此方法管理依賴項值的類的示例。
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies(); /// [見2.9.1]
}
/// 經過調用Element.didChangeDependencies通知全部依賴的Elements,這個widget已經更改。
/// 此方法只能在 build 階段調用。 一般當 Inherited widget 被重建時,這個方法會自動調用,例如,做爲在 Inherited widget上方
/// 調用State.setState的結果
@override
void notifyClients(InheritedWidget oldWidget) {
for (final Element dependent in _dependents.keys) {
/// ...
notifyDependent(oldWidget, dependent);
}
}
}
複製代碼
void didChangeDependencies() {
markNeedsBuild();
}
複製代碼
看到 markNeedsBuild
是否是很熟悉。沒錯,就是咱們常用 setState
,調用同樣的方法。
void setState(VoidCallback fn) {
// ...
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
}
複製代碼
上面咱們發現 notifyClients
,遍歷循環 _dependents.keys
,調用 Element.didChangeDependencies
更新依賴。咱們看下 dependents
是怎麼來的呢?咱們看 updateDependencies
註釋,是調用 context.dependOnInheritedWidgetOfExactType
添加 Element
。
Element
是BuildContext
具體實現類
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
複製代碼
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
複製代碼
下圖,只是簡單畫了 InheritedElement
更新依賴組件的大體流程圖,方便你們吸取,具體的細節還須要本身去挖掘。
dependOnInheritedWidgetOfExactType
經過 inheritedWidgets[T]
獲取到對應的 InheritedElement
,當 InheritedElement
不爲空,接着調用 InheritedElement
的 updateDependencies
,把當前 Element
注入到要獲取 InheritedElement
的 _dependents.keys
集合,接着返回 InheritedElement
的 widget
對象。當InheritedWidget
更新時,先經過 updateShouldNotify
判斷當前 InheritedElement
是否能進行 updated
,當值爲 true
時,經過循環遍歷 _dependents.keys
集合,來更新全部依賴的 widget
。
前面寫了一個簡單的 計數器
,同時也介紹了 InheritedWidget
更新依賴 widget
的原理分析。那咱們能不能基於 InheritedWidget
實現一個本身的 Provider
功能呢?接下來,咱們基於以前的 計數器
的代碼,一步一步優化,實現一個咱們本身的 Provider
。
經過上面咱們能發現,在主頁面直接調用 setState
,是比較消耗性能,應該把 incrementCounter
操做剝離出去,單獨一個 model
,這樣彷佛更符合實際的開發。
新建 CountModel
用來進行數據處理及頁面更新操做,有點相似 android
中 viewModel
。
class CountModel with ChangeNotifier {
int _count = 0;
int get count => _count;
void incrementCounter() {
_count++;
notifyListeners();
}
}
複製代碼
初始化傳入數據源 T
, 繼承 ChangeNotifier
,監聽 T
notifyListeners
時,刷新 CountProvider
,順便把 dependOnInheritedWidgetOfExactType
從 CountScope
移到 CountProvider
類中。
typedef WidgetBuilder = Widget Function(BuildContext context);
class CountProvider<T extends ChangeNotifier> extends StatefulWidget {
const CountProvider({
Key key,
this.value,
this.builder,
}) : super(key: key);
final T value;
final WidgetBuilder builder;
static T of<T extends ChangeNotifier>(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CountScope<T>>().value;
}
@override
_CountProviderState<T> createState() => _CountProviderState<T>();
}
class _CountProviderState<T extends ChangeNotifier> extends State<CountProvider<T>> {
void _changeValue() {
setState(() {});
}
@override
void initState() {
super.initState();
widget.value?.addListener(_changeValue);
}
@override
void dispose() {
widget.value?.removeListener(_changeValue);
super.dispose();
}
@override
void didUpdateWidget(covariant CountProvider<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.value != widget.value) {
oldWidget.value?.removeListener(_changeValue);
}
widget.value?.addListener(_changeValue);
}
@override
Widget build(BuildContext context) {
print('_CountProviderState build');
return CountScope<T>(
value: widget.value,
child: Builder(
builder: (BuildContext context) {
return widget.builder(context);
},
),
);
}
}
複製代碼
class CountScope<T extends ChangeNotifier> extends InheritedWidget {
const CountScope({@required this.value, Widget child}) : super(child: child);
final T value;
@override
bool updateShouldNotify(CountScope<T> oldWidget) {
return true;
}
}
複製代碼
class CountWidget extends StatefulWidget {
const CountWidget({Key key}) : super(key: key);
@override
_CountWidgetState createState() => _CountWidgetState();
}
class _CountWidgetState extends State<CountWidget> {
@override
Widget build(BuildContext context) {
print('_CountWidgetState build');
return Container(
child: Text(
'${CountProvider.of<CountModel>(context).count}',
style: Theme.of(context).textTheme.headline4,
),
);
}
}
複製代碼
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
print('_MyHomePageState build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
CountProvider<CountModel>(
value: CountModel(),
builder: (BuildContext context) {
return Column(children: [
const CountWidget(),
ClickButton(),
]);
},
),
],
),
),
);
}
}
複製代碼
class ClickButton extends StatelessWidget {
const ClickButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('ClickButton build');
return ElevatedButton(
onPressed: () {
print('--- 點擊 ---');
CountProvider.of<CountModel>(context).incrementCounter();
},
child: const Icon(Icons.add),
);
}
}
複製代碼
當咱們點擊 +
時,能正常更新數據,咱們看 log
會發現, MyHomePage
頁面沒有從新進行 build
,若是咱們在MyHomePage
的Column
組件裏面,再添加一個不依賴 CountProvider
的組件,會發生什麼?
class Nothing extends StatefulWidget {
const Nothing({Key key}) : super(key: key);
@override
_NothingState createState() => _NothingState();
}
class _NothingState extends State<Nothing> {
@override
Widget build(BuildContext context) {
print('_NothingState build');
return Container(
child: const Text('我是一個不依賴的 widget'),
);
}
}
複製代碼
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
print('_MyHomePageState build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
CountProvider<CountModel>(
value: CountModel(),
builder: (BuildContext context) {
return Column(children: [
const CountWidget(),
Nothing(), /// 新增
ClickButton(),
]);
},
),
],
),
),
);
}
}
複製代碼
結果以下圖:
能夠發現,每次咱們點擊 +
,調用 CountModel
進行 notifyListeners
更新時,都會致使 CountProvider
的子 Widget
所有刷新,不論是否依賴。
如今咱們會發現有三個問題:
Widget
調用 CountModel
方法時,能夠不進行依賴綁定?Flutter Hot Reload
按鈕,會發現計數器的 1
,會變成 0
,這合理嗎?widget
,不更新 InheritedElement
組件下,全部的子 widget
?由於咱們是在 CountProvider
中監聽 CountScope
的數據源變化,調用 setState
刷新整個 CountProvider
,致使子 widget
也都所有更新,因此,咱們應該把數據源更新監聽移到 CountScope
,讓 CountScope
通知刷新依賴的 widget
比較合理。
class CountScope<T extends ChangeNotifier> extends InheritedWidget {
const CountScope({@required this.value, Widget child}) : super(child: child);
final T value;
@override
bool updateShouldNotify(CountScope<T> oldWidget) {
/// 由於是 InheritedElement 進行刷新,因此,這裏能夠設置爲 false
return false;
}
@override
CountScopeElement<T> createElement() {
return CountScopeElement<T>(value, this);
}
}
class CountScopeElement<T extends ChangeNotifier> extends InheritedElement {
CountScopeElement(this.value, InheritedWidget widget) : super(widget) {
value?.addListener(_handleUpdate);
}
final T value;
void _handleUpdate() {
markNeedsBuild();
}
@override
void unmount() {
value?.removeListener(_handleUpdate);
super.unmount();
}
@override
Widget build() {
notifyClients(widget);
return super.build();
}
}
複製代碼
新建 CountScopeElement
繼承 InheritedElement
,在 CountScope
的 createElement
返回自定義 CountScopeElement
類。在 CountScopeElement
實現數據源變化監聽,在銷燬時調用移除監聽。
_handleUpdate
調用 markNeedsBuild
,這個方法咱們應該都很熟悉,也就是 setState
調用後,調用一樣的方法,把 CountScopeElement
標記爲 dirty
。build
調用 notifyClients(widget)
,可能會人有不明白,爲何這裏要調用 notifyClients
?
經過 [2.8小節]
咱們能夠發現,當 CountScopeElement
進行 build
時,其實這裏的 child
沒有更新,仍是同一個對象。[見2.5小節]
,當 child.widget == newWidget
時,是不進行 child.update()
操做,也就沒有後續的一串依賴更新操做。因此,這裏要重寫 build
,手動調用內部的 notifyClients
,通知依賴更新操做。
經過 [1.1小節]
咱們知道,獲取 InheritedWidget
是 dependOnInheritedWidgetOfExactType
,並建立依賴關係。咱們經過 getElementForInheritedWidgetOfExactType
獲取 InheritedElement
,這個過程,不建立依賴關係,彷佛能解決須要依賴的問題,咱們修改 CountProvider
的 of
以下:
static T of<T extends ChangeNotifier>(BuildContext context, {bool listen = true}) {
final InheritedElement inheritedElement = context.getElementForInheritedWidgetOfExactType<CountScope<T>>();
final CountScopeElement<T> countScopeElement = inheritedElement as CountScopeElement<T>;
if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return countScopeElement.value;
}
複製代碼
實現思路,獲取到 CountScopeElement
,若是 listen = true
時,則進行依賴更新,最後返回 CountScopeElement.value
。
class CountProvider<T extends ChangeNotifier> extends StatefulWidget {
const CountProvider({
Key key,
this.value,
this.builder,
}) : super(key: key);
/// 數據源
final T value;
final WidgetBuilder builder;
static T of<T extends ChangeNotifier>(BuildContext context, {bool listen = true}) {
final InheritedElement inheritedElement = context.getElementForInheritedWidgetOfExactType<CountScope<T>>();
final CountScopeElement<T> countScopeElement = inheritedElement as CountScopeElement<T>;
if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return countScopeElement.value;
}
@override
_CountProviderState<T> createState() => _CountProviderState<T>();
}
class _CountProviderState<T extends ChangeNotifier> extends State<CountProvider<T>> {
@override
Widget build(BuildContext context) {
print('_CountProviderState build');
return CountScope<T>(
value: widget.value,
child: Builder(
builder: (BuildContext context) {
return widget.builder(context);
},
),
);
}
}
複製代碼
最終效果是可行的,也解決了 [3.1.8小節]
提出的三個問題。上面的代碼看起來彷佛沒什麼問題,若是 _MyHomePageState
在進行 setState
操做,會發現什麼事情?
class _MyHomePageState extends State<MyHomePage> {
void _refresh() {
setState(() {});
}
@override
Widget build(BuildContext context) {
print('_MyHomePageState build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
CountProvider<CountModel>(
value: CountModel(),
builder: (BuildContext context) {
return Column(children: [
const CountWidget(),
Nothing(),
ElevatedButton(
onPressed: () {
print('--- 點擊 ---');
CountProvider.of<CountModel>(context, listen: false).incrementCounter();
},
child: const Icon(Icons.add),
),
]);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _refresh,
tooltip: 'refresh',
child: const Icon(Icons.refresh),
),
);
}
}
複製代碼
每次點擊 refresh
,也會刷新 CountProvider
的依賴的子 widget
,這彷佛並無達到咱們的要求。那該怎麼解決呢?
抽象類, CountScopeElement
剝離出來須要用到功能和 value
,方便後期擴展。
abstract class InheritedBuildContext<T> {
void needsBuild();
T get value;
}
複製代碼
代理類 ,CountScopeElement
中方法的實現,value
的賦值。
abstract class Delegate<T> {
CountScopeElement<T> element;
T get value;
}
複製代碼
Delegate
的實現類,主要實現了數據的監聽,通知 InheritedElement
刷新依賴。
class CountDelegate<T extends ChangeNotifier> extends Delegate<T> {
CountDelegate({this.notifier});
T notifier;
@override
T get value {
notifier.addListener(() {
element.needsBuild();
});
return notifier;
}
}
複製代碼
class CountScope<T> extends InheritedWidget {
const CountScope({@required this.delegate, Widget child}) : super(child: child);
final Delegate<T> delegate;
@override
bool updateShouldNotify(CountScope<T> oldWidget) {
return false;
}
@override
CountScopeElement<T> createElement() {
return CountScopeElement<T>(delegate, this);
}
}
class CountScopeElement<T> extends InheritedElement with InheritedBuildContext<T> {
CountScopeElement(this.delegate, InheritedWidget widget) : super(widget);
final Delegate<T> delegate;
bool _dirty = false;
@override
CountScope<T> get widget => super.widget as CountScope<T>;
@override
void performRebuild() {
delegate.element = this;
super.performRebuild();
}
@override
Widget build() {
if (_dirty) {
_dirty = false;
notifyClients(widget);
}
return super.build();
}
@override
void needsBuild() {
_dirty = true;
markNeedsBuild();
}
@override
T get value => delegate.value;
}
複製代碼
主要修改,當須要從新 build
時,_dirty
標記爲 true
,當 build
完成時,再設置爲 false
,避免當CountScope
刷新,致使依賴的子 widget
也所有刷新。
集成 SingleChildStatelessWidget
。
class CountProvider<T extends ChangeNotifier> extends SingleChildStatelessWidget {
CountProvider({Key key, this.value, this.builder})
: _delegate = CountDelegate<T>(
notifier: value,
),
super(key: key);
final T value;
final WidgetBuilder builder;
final Delegate<T> _delegate;
static T of<T extends ChangeNotifier>(BuildContext context, {bool listen = true}) {
final InheritedElement inheritedElement = context.getElementForInheritedWidgetOfExactType<CountScope<T>>();
final CountScopeElement<T> countScopeElement = inheritedElement as CountScopeElement<T>;
if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return countScopeElement.value;
}
@override
Widget buildWithChild(BuildContext context, Widget child) {
print('CountProvider buildWithChild');
return CountScope<T>(delegate: _delegate, child: Builder(builder: builder));
}
}
複製代碼
最終結果:當 CountScope
從新 rebuild
時,依賴的子 widget
不會跟着一塊兒 rebuild
。
小結:這裏優化三個方案,也只是帶你們發現問題,一步一步去解決問題。其實,跟你們的業務結合的話,這上面的代碼,或許還會有不知足務求的地方。可是,無論什麼框架,也都是一步一步優化過來的。只要有瞭解決問題的思路和想法,我相信,任何問題都不在是問題。
前面分析 InheritedWidget
的源碼,調用流程等,在實際開發過程當中,咱們可能不會直接接觸 InheritedWidget
,可是,仍是能發現一些系統的組件,使用 InheritedWidget
進行封裝開發。例如:Theme
、Focus
、ButtonBarTheme
等,就不一一舉例了,你們能夠去看下源碼。寫這篇文章,一方面是爲了鞏固知識,方便往後查看、回憶知識點。另外一方便也是拋磚引玉,讓更多人瞭解 InheritedWidget
,若是你們認真看,其實仍是會發現一些 provider
的影子。
若是文章有什麼不足的地方,歡迎批評指正,謝謝~