目前,开发的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
的影子。
若是文章有什么不足的地方,欢迎批评指正,谢谢~