Flutter状态管理——InheritedWidget数据共享的原理分析
Posted 一叶飘舟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter状态管理——InheritedWidget数据共享的原理分析相关的知识,希望对你有一定的参考价值。
InheritedWidget是什么
在InheritedWidget的文档注释中是这么描述的:
Base class for widgets that efficiently propagate information down the tree.
在渲染树中有效向子树传递信息的基类。
从app的入口:
void main()
runApp(MyApp());
// runApp(CustomInheritedWidget());
就开始构建一颗渲染树,MyApp()可看作该树的根节点,如果将该根节点的Widget设为一个InheritedWidget,那么其子树中所有的子树子节点都可获取到该InheritedWidget内共享到的数据。当InheritedWidget中的数据发生改变时,所有依赖该数据的子widget都会重新build。
有什么用
围绕 ”共享,共变“的特点
- 统一主题设置,更改主题后,所有的子页面都会即时更改。
- 项目基础数据,如用户信息、权限等公共数据的共享。
- 状态管理
- ......
核心使用流程
- 自定义CustomInheritedWidget 继承 InheritedWidget,将需要共享的数据设为成员变量。重写updateShouldNotify方法,在该方法内明确当什么情况下会让那些依赖此共享数据的widget重新build。
- 在需要依赖此数据的widget中获取该共享数据,CustomInheritedWidget.of(context).data.toString(),data为自定义的共享数据,可以是其他。重写didChangeDependencies方法,当该共享数据发生变化时,触发此方法。
- 将依赖者成为CustomInheritedWidget的一个子widget
源码分析
- InheritedWidget,参数为一个child,将依赖者设为为child或此child的子孙。Widget的核心作用是为element配置数据,这里的InheritedElement才是真正发挥作用的类。updateShouldNotify方法也是重写的InheritedElement中的方法,作用是是否通知那些依赖者进行更新。
abstract class InheritedWidget extends ProxyWidget
const InheritedWidget( Key key, Widget child )
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
/// Whether the framework should notify widgets that inherit from this widget.
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
- 进入InheritedElement
class InheritedElement extends ProxyElement
...
final Map<Element, Object> _dependents = HashMap<Element, Object>();
@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;
@override
void debugDeactivated()
assert(()
assert(_dependents.isEmpty);
return true;
());
super.debugDeactivated();
/// Returns the dependencies value recorded for [dependent]
@protected
Object getDependencies(Element dependent)
return _dependents[dependent];
/// Sets the value returned by [getDependencies] value for [dependent].
@protected
void setDependencies(Element dependent, Object value)
_dependents[dependent] = value;
/// Called by [dependOnInheritedWidgetOfExactType] when a new [dependent] is added.
@protected
void updateDependencies(Element dependent, Object aspect)
setDependencies(dependent, null);
/// Called by [notifyClients] for each dependent.
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent)
dependent.didChangeDependencies();
/// Calls [Element.didChangeDependencies] of all dependent elements, if
/// [InheritedWidget.updateShouldNotify] returns true.
@override
void updated(InheritedWidget oldWidget)
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
/// Notifies all dependent elements that this inherited widget has changed, by
/// calling [Element.didChangeDependencies].
@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);
_dependents: 维护一个map,存放所有依赖者的引用。
**_inheritedWidgets:**这是祖先类Element的一个属性,当是InheritedElement才会起作用,保存的是祖先节点中所有出现的InheritedWidget与InheritedElement的对应关系_inheritedWidgets[widget.runtimeType] = this,这里的widget.runtimeType是Object中的属性,唯一代表此类,这里的this是InheritedElement。
**_updateInheritance():**该方法也是Element的方法,在Element的mount()内会调用,目的就是更新_inheritedWidgets。需要注意,该方法在Element中有默认实现,是这样的:
void _updateInheritance()
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
也就是说判断父节点中是否有_inheritedWidgets,有的话,赋值给当前element的_inheritedWidgets,如此这样,每一层的element都会保留上一层的_inheritedWidgets,这也就是为什么InheritedWidget能一直向下传递数据的原因。 而InheritedElement重写了该方法。主要是这段代码:_inheritedWidgets[widget.runtimeType] = this,更新映射。将当前InheritedWidget页加入到该_inheritedWidgets 中。
**getDependencies():**获取所有依赖者 **setDependencies():**添加新的依赖者。一般在自定义InheritedWidget时,都会定义一个静态方法of,用于获取该自定义的InheritedWidget,如:
static MyInheritedWidget of(BuildContext context)
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
深入context.inheritFromWidgetOfExactType方法内部:
InheritedWidget inheritFromWidgetOfExactType(Type targetType, Object aspect )
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null)
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
_hadUnsatisfiedDependencies = true;
return null;
继续深入inheritFromElement():
@override
InheritedWidget inheritFromElement(InheritedElement ancestor, Object aspect )
return dependOnInheritedElement(ancestor, aspect: aspect);
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, Object aspect )
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
@protected
void updateDependencies(Element dependent, Object aspect)
setDependencies(dependent, null);
最终定格在:setDependencies(dependent, null); 由此可知,当依赖者使用of获取自定义InheritedWidget时,就将自己添加进了依赖者集合当中。 **updateDependencies():**更新指定依赖者 notifyDependent():,调用依赖者的didChangeDependencies方法,告知依赖的数据发生了变化,这个方法会被notifyClients()方法批量调用。 notifyClients():,批量告知依赖者,数据发生了变化。该方法是在ProxyElemnt中被定义,在InheritedElement中被重写了。当自定义的InheritedWidget内数据发生变化时,会通过重写的方法updateShouldNotify去定义是否需要通知依赖者更新,updateShouldNotify如:
@override
bool updateShouldNotify(MyInheritedWidget oldWidget)
// 新旧数据不一致时,返回true,通知依赖本widget的子widget,此时子widget中的didChangeDependencies方法会被调用
return oldWidget.data != data;
当新旧data不同时要进行通知,这里的return可以根据实际情况定义。 那什么时刻调用的updateShouldnotify方法呢?在InheritedElement中的updated方法内:
@override
void updated(InheritedWidget oldWidget)
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
updated()方法是在ProxyElement中被定义的,这里调用super.notifyClients()方法,在ProxyElement中是这样实现的:
@protected
void updated(covariant ProxyWidget oldWidget)
notifyClients(oldWidget);
notifyClients()在ProxyELement中是空实现,在InheritedElement进行重写。 如此就将整个流程串联起来了。
InheritedWidget内部流程
- 自定义InheritedWidget,设定共享数据,重写updateShouldNotify方法,提供of静态方法。
- 将依赖者成为InheritedWidget的子孙,依赖者调用of方法获取共享数据时,内部就将该依赖者添加依赖者名单_dependents中了。
- element树在构建过程中,mount时会通过_updateInheritance方法将_inheritedWidgets层层下传,在下传过程中,非InheritedWidget类型的widget会将parent的_inheritedWidgets直接赋给自己,而InheritedWidget类型的widget会将parent的_inheritedWidgets赋给自己,并将自己添加进去。
- 共享数据发生变化,updated方法判断updateShouldNotify是否为true,为true就调用notifyClients方法,notifyClients内部在调用dependent.didChangeDependencies();最终调用到依赖者的didChangeDependencies方法内。
以上是关于Flutter状态管理——InheritedWidget数据共享的原理分析的主要内容,如果未能解决你的问题,请参考以下文章