TL;博士
不要在updateShouldNotify方法中使用繁重的计算,并在创建小部件时使用const而不是 new
首先,我们应该了解什么是Widget、Element和Render对象。
- 渲染对象是实际在屏幕上渲染的对象。它们是可变的,包含绘画和布局逻辑。渲染树与 Web 中的文档对象模型 (DOM) 非常相似,您可以将渲染对象视为该树中的 DOM 节点
- 小部件- 是对应该呈现的内容的描述。它们是不可变的且便宜的。因此,如果 Widget 回答问题“什么?”(声明性方法),则 Render 对象回答问题“如何?”(命令式方法)。来自网络的类比是“虚拟 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具有相应的 Element 对象。它是InheritedElement。每次我们构建新的小部件时,InheritedElement都会在小部件上调用updateShouldNotify (在祖先上调用setState )。当updateShouldNotify返回true时,InheritedElement 会遍历依赖项(?) 并在其上调用方法didChangeDependencies。
InheritedElement 在哪里获取依赖项?这里我们应该看一下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))
- 我们返回祖先的小部件作为inheritFromWidgetOfExactType的结果(返回祖先.widget)
所以现在我们知道了 InheritedElement 从哪里获取它的依赖项。
现在让我们看看didChangeDependencies方法。每个元素都有这个方法:
void didChangeDependencies() {
assert(_active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}
正如我们所看到的,这个方法只是将一个元素标记为脏,这个元素应该在下一帧重建。重建意味着调用方法构建在对应的小部件元素上。
但是“当我重建 InheritedWidget 时会重建整个子树吗?”。在这里,我们应该记住 Widget 是不可变的,如果您创建新的 Widget,Flutter 将重建子树。我们该如何解决?
- 手动缓存小部件(手动)
- 使用const因为 const创建唯一的值/类实例