介绍
InheritedWidget
是flutter中用来从上层传递数据到下层的组件(类似于React中的Context)。对于简单的情况,如果需要将数据传递到下层,可以通过参数的方式。如下所示
class ParentWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
var value = "this is the value";
return ChildWidget(value: value);
}
}
class ChildWidget extends StatelessWidget {
String value;
ChildWidget({Key key, this.value}): super(key);
@override
Widget build(BuildContext context) {
return Text(value);
}
}
这种方式在Widget层级不是很深的时候比较方便,但是如果Widget层级很多,就需要从上层一层一层的向下传递,容易造成混乱。所以flutter提供了InheritedWidget
方便上层Widget跨层级为下层子节点提供数据。
使用方式
可以创建一个Widget继承自InheritedWidget
, 在Widget中保存需要共享给子Widget的值。创建一个静态方法(名称通常为of
),该方法接收BuildContext
参数,通过调用BuildContext
的dependOnInheritedWidgetOfExactType
可以从下层找到该Widget,从而获取Widget中的值。
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FrogColor>();
}
@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
在定义InheritedWidget
的时候,还需要重写updateShouldNotify
方法。该方法作用是,当InheritedWidget
重新build的时候,是否要通知依赖于该Widget的子Widget也要重新build。一般是在这个方法中比较InheritedWidget
所持有的值是否变化,如果变化了,就通知子Widget重新build,以更新状态。
下层获取可以通过of
方法获取值
Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FrogColor(
color: Colors.green,
child: Center(
child: ChildWidget(),
)
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
class ChildWidget extends StatelessWidget {
Widget build(BuildContext context) {
var color = FrogColor.of<FrogColor>(context).color;
return Text(
"color from parent",
style: TextStyle(
color:color
));
}
}
可以看到,在子Widget中并没有设置color参数,而是通过FrogColor.of(context).color
获取到了祖先Widget的值,即使中间跨越了其他Widget。那么是通过什么来访问到祖先Widget的数据呢,关键点就是dependOnInheritedWidgetOfExactType
方法。
源码分析
该方法在BuildContext
中,而BuildContext
的实现类是Element
,我们来看下这个方法的源码。(还有一个inheritFromWidgetOfExactType
方法,这个方法的实现和dependOnInheritedWidgetOfExactType
一样,在1.12版本中被deprecated了)
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
方法非常简单,首先使用泛型T作为参数(Dart中的泛型比Java强大一些,没有被擦除),从_inheritedWidgets
中获取类型为InheritedElement
的ancestor
对象,该对象就是InheritedWidget
所对应的Element
对象。如果这个ancestor
对象不为空,再调用dependOnInheritedElement
方法。
Map<Type, InheritedElement> _inheritedWidgets;
_inheritedWidgets
是在Element
类中定义的一个Map, 保存了所有的所有的祖先InheritedWidget
。这个_inheritedWidgets
是从哪里初始话的呢?
Element中的_inheritedWidgets
初始化流程
通过搜索可以找到在_updateInheritance
方法中会更新_inheritedWidgets
Map,_updateInheritance
是也是在Element类中的定义,默认是实现是将parent的值赋给自己。
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
在InheritedElement中重写了_updateInheritance
方法,不会直接赋值,而是通过HashMap.from方法重新创建了一个Map,并且通过_inheritedWidgets[widget.runtimeType] = this
; 将InheritedElement自己保存到这个Map中。
@override
void _updateInheritance() {
assert(_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;
}
通过上面的方式,就可以一层一层的传递保存InheritedEelement
的Map,非InheritedWidget
直接保存parent的_inheritedWidgets
的引用,是同一个Map对象。而InheritedWidget
对应的InheritedElement
会创建一个新的Map,并将对应widget的runtimeType
作为key,element
作为Value保存在Map中。所以底层的Element的_inheritedWidgets
Map中包含所有类型为InheritedWidget
的祖先节点信息。就可以通过Widget 的Type访问到Element,再通过Element访问到Widget。
_updateInheritance
方法在Element被挂载到Element Tree时(mount)或者activate
的时候被调用。
void mount(Element parent, dynamic newSlot) {
...
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner;
final Key key = widget.key;
if (key is GlobalKey) {
key._register(this);
}
_updateInheritance();
...
}
void activate() {
...
final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
_active = true;
_dependencies?.clear();
_hadUnsatisfiedDependencies = false;
_updateInheritance();
...
if (_dirty)
owner.scheduleBuildFor(this);
if (hadDependencies)
didChangeDependencies();
}
dependOnInheritedElement
分析完_inheritedWidgets
的初始化流程,我们回头再看dependOnInheritedElement
方法。
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
在dependOnInheritedElement
中首先会将ancestor
保存在_dependencies
中,再调用ancestor
的
updateDependencies
方法。
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
而updateDependencies
方法再调用setDependencies
方法
final Map<Element, Object> _dependents = HashMap<Element, Object>();
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
setDependencies
方法底层的Element保存在_dependents
Map中。
以上就是通过BuildContext的dependOnInheritedWidgetOfExactType
访问到祖先InhritedWidget的过程。
updateShouldNotify方法
在定义InheritedWidget时还需要重写updateShouldNotify
方法,这个方法会在InheritedEelement中的updated
方法中被调用
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
调用updateShouldNotify
方法,如果返回true就再调用super.updated(oldWidget)
方法,否则,不执行任何操作。
InheritedElement
的父类是ProxyElement
,ProxyElement
的updated
方法会调用notifyClinets(oldWidget)
方法
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
notifyClinets(oldWidget)
方法是ProxyElement
定义的抽象方法,在InheritedElement
类中有具体的实现如下:
@override
void notifyClients(InheritedWidget oldWidget) {
...
for (final Element dependent in _dependents.keys) {
...
notifyDependent(oldWidget, dependent);
}
}
会循环遍历_dependents
列表,调用notifyDependent(oldWidget, dependent)
方法
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
notifyDependent
方法会直接调用dependent
的didChangeDependencies
方法,这个方法定义在Element
类中,实现如下
void didChangeDependencies() {
...
markNeedsBuild();
}
didChangeDependencies
方法会调用Element的markNeedsBuild
方法,而markNeedsBuild
方法会将该element
标记为dirty,在下一帧将会重新build。
总结
将特定的值保存在InheritedWidget
中,Elemment树中的每一个节点都会保存祖先节点的InheritedWidget信息,所以可以在子组件中可以查询到特定类型的InheritedWidget,并获取到其所保存的值。当InheritedWidget
被重新build
时,会根据updateShouldNotify
方法决定是否通知依赖该InheritedWidget的子Widget更新。
在flutter framework源码中大量使用了InheritedWidget实现数据的传递和共享,通过源码分析可以对其原理进行深入的理解但在我们日常开发中,还是推荐使用Provider,Provider提供了更简单的API和更强大的功能。