TL;DR
在updateShouldNotify方法中不要使用重计算,并且在创建小部件时使用const而非new。
首先,我们应该了解什么是Widget、Element和Render对象。
- Render对象是实际渲染在屏幕上的内容。它们是可变的,包含绘制和布局逻辑。渲染树与Web中的文档对象模型(DOM)非常相似,您可以将渲染对象视为树中的DOM节点。
- Widget是描述应该渲染什么的一种方式。它们是不可变的且廉价的。因此,如果Widget回答“什么?”这个问题(声明式方法),那么Render对象就回答“如何?”这个问题(命令式方法)。Web上的类比是“虚拟DOM”。
- Element/BuildContext是Widget和Render对象之间的代理。它包含有关小部件在树中位置的信息*,以及如何在对应小部件更改时更新Render对象的信息。
现在我们准备深入研究InheritedWidget和BuildContext的方法inheritFromWidgetOfExactType。
作为示例,我建议我们考虑Flutter文档中关于InheritedWidget的这个例子:
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.inheritFromWidgetOfExactType(FrogColor);
}
@override
bool updateShouldNotify(FrogColor old) {
return color != old.color;
}
}
InheritedWidget - 仅是一个小部件,我们的情况下实现了一个重要的方法 - updateShouldNotify。
updateShouldNotify - 一个函数,接受一个参数oldWidget并返回一个布尔值:true或false。
像任何小部件一样,InheritedWidget都有相应的元素对象。它是InheritedElement。 InheritedElement在每次构建新小部件(在祖先上调用setState)时调用小部件上的updateShouldNotify。当updateShouldNotify返回true时,InheritedElement会遍历其dependencies,并在其上调用方法didChangeDependencies。
InheritedElement从哪里获取dependencies?在这里,我们应该看看inheritFromWidgetOfExactType方法。
inheritFromWidgetOfExactType - 此方法定义在BuildContext中,每个Element都实现了BuildContext接口(Element == BuildContext)。因此,每个Element都有这个方法。
让我们看看inheritFromWidgetOfExactType的代码:
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
在这里,我们尝试通过类型映射到 _inheritedWidgets 中的祖先。
如果找到祖先,则调用 inheritFromElement。
inheritFromElement 的代码:
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
- 我们将祖先元素添加为当前元素的依赖项 (_dependencies.add(ancestor))
- 我们将当前元素添加为祖先元素的依赖项 (ancestor.updateDependencies(this, aspect))
- 我们以祖先元素的widget作为inheritFromWidgetOfExactType方法的返回值 (return ancestor.widget)
现在我们知道InheritedElement从哪里获取其依赖关系了。
现在让我们看一下didChangeDependencies方法。每个Element都有这个方法:
void didChangeDependencies() {
assert(_active);
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}
如我们所见,这种方法只是标记一个元素为dirty,并且这个元素应该在下一帧中重建。重建意味着在相应的小部件元素上调用build方法。
那么,“当我重建InheritedWidget时整个子树都会被重建”怎么办呢?
在这里,我们应该记住,小部件是不可变的,如果您创建了新的小部件,Flutter将重新构建子树。我们该如何解决呢?
- 手动缓存小部件(手动)
- 使用const,因为const只创建值/类的一个实例