InheritedWidget
简介
InheritedWidget
提供了一种在widget树中从上到下共享数据的方式,能实现组件跨级传递数据。比如我们在应用的根widget中通过InheritedWidget
共享了一个数据,那么我们便可以在任意子widget中来获取该共享的数据!
应用:如Flutter SDK中正是通过InheritedWidget
来共享Theme
和Local
(当前语言环境)信息
数据传输
示例:
1 | class ShareDataWidget extends InheritedWidget { |
原理:
dependOnInheritedWidgetOfExactType
方法将在下文解析getElementForInheritedWidgetOfExactType
方法:1
2
3
4
5
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
return ancestor;
}通过上述两个函数既可以获取到InheritedElement实例,继而拿到了共享的数据
刷新机制
InheritedElement和Element之间有一些交互,实际上自带了一套刷新机制
InheritedElement存子节点Element:
_dependents
,这个变量用来存储需要刷新的子Element1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class InheritedElement extends ProxyElement {
InheritedElement(InheritedWidget widget) : super(widget);
final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
void debugDeactivated() {
assert(() {
assert(_dependents.isEmpty);
return true;
}());
super.debugDeactivated();
}
Object? getDependencies(Element dependent) {
return _dependents[dependent];
}
void setDependencies(Element dependent, Object? value) {
_dependents[dependent] = value;
}
void updateDependencies(Element dependent, Object? aspect) {
setDependencies(dependent, null);
}
}InheritedElement刷新子Element
- 在
notifyClients
方法中,循环_dependents
存储的Element,传入notifyDependent
- 在
notifyDependent
中,传入Element调用自身didChangeDependencies
方法 - Element的
didChangeDependencies
方法会调用markNeedsBuild
,来刷新自身
- 在
InheritedWidget的子节点是如何将自身Element添加到
_dependents
dependOnInheritedWidgetOfExactType()
源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
InheritedWidget dependOnInheritedWidgetOfExactType({ 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;
}
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
可以看到在调用dependOnInheritedWidgetOfExactType
时,InheritedWidget
和依赖它的子widget注册了依赖关系,之后当InheritedWidget
发生变化时,就会更新依赖它的子widget,也就是会调用这些子widget的didChangeDependencies()
和build()
。
- 在
dependOnInheritedElement
方法中,会传入InheritedElement
实例ancestor
ancestor
会调用updateDependencies
方法,将自身的Element实例传入,这样就添加到_dependents
中了可以发现:调用
dependOnInheritedWidgetOfExactType()
和getElementForInheritedWidgetOfExactType()
的区别就是前者会注册依赖关系,而后者不会
didChangeDependencies
State
的生命周期之一,它会在“依赖”发生变化时被Flutter框架调用,而这个“依赖”指的就是子widget是否使用了父widget中InheritedWidget
的数据!如果使用了,则代表子widget有依赖;如果没有使用则代表没有依赖。这种机制可以使子widget在所依赖的InheritedWidget
变化时来更新自身!在数据发生变化时只对使用该数据的widget更新是合理并且性能友好的。
应该在didChangeDependencies中做什么?
如果需要在依赖改变后执行一些昂贵的操作,比如网络请求,这时最好的方式就是在此方法中执行,这样可以避免每次build()
都执行这些昂贵操作。
Provider
Provider
是对InheritedWidget
组件的上层封装,使其更易用,更易复用。
原理:基于发布者-订阅者模式,Model变化后会自动通知ChangeNotifierProvider
(订阅者),ChangeNotifierProvider
内部会重新构建InheritedWidget
,而依赖该InheritedWidget
的子widget
就会更新。
使用Provider 优点:
- 业务代码更关注数据了,只要更新Model,则UI会自动更新,而不用在状态改变后去手动调用
setState()
来显示更新页面 - 数据改变的消息传递被屏蔽了,我们无需手动去处理状态改变事件的发布和订阅,这一切都被封装在Provider中了
- 在大型复杂应用中,尤其是需要全局共享的状态非常多时,使用Provider将会大大简化代码逻辑,降低出错的概率,提高开发效率
参考: