使用InheritedWidget的正确方法是什么?到目前为止,我明白它让你有机会沿着Widget树传播数据.在极端情况下,如果你把它作为RootWidget,它将可以从所有Routes树中的所有Widgets访问,这很好,因为不知何故我必须使我的ViewModel/Model可以访问我的Widgets而不必诉诸于全局或Singletons.
但是InheritedWidget是不可变的,那么如何更新呢?更重要的是我的状态窗口小部件如何触发重建他们的子树?
不幸的是,文档在这里非常不清楚,经过大量讨论后,似乎没有人真正知道使用它的正确方法.
我添加了Brian Egan的一句话:
是的,我认为这是一种沿树传播数据的方法.从API文档中我发现令人困惑的是:
"以这种方式引用时,继承的小部件将导致使用者在继承的小部件本身更改状态时重建."
当我第一次阅读本文时,我想:
我可以在InheritedWidget中填充一些数据并稍后进行修改.当发生突变时,它将重建引用我的InheritedWidget的所有Widgets我发现的内容:
为了改变InheritedWidget的状态,你需要将它包装在StatefulWidget中然后实际上改变StatefulWidget的状态并将这些数据传递给InheritedWidget,后者将数据传递给它的所有子节点.但是,在这种情况下,它似乎重建了StatefulWidget下面的整个树,而不仅仅是引用InheritedWidget的Widgets.那是对的吗?或者它会以某种方式知道如果updateShouldNotify返回false,如何跳过引用InheritedWidget的Widgets?
Rém*_*let 81
问题来自你的引用,这是不正确的.
正如您所说,InheritedWidgets与其他小部件一样,是不可变的.因此他们不会更新.它们是重新创造的.
问题是:InheritedWidget只是一个简单的小部件,只能保存数据.它没有任何更新逻辑或任何.但是,像任何其他小部件一样,它与一个小部件相关联Element.你猜怎么着 ?这个东西是可变的,只要有可能就会重复使用它!
更正的报价将是:
当以这种方式引用时,InheritedWidget将导致使用者在与InheritedElement关联的InheritedWidget更改时重建.
关于如何将小部件/元素/渲染框组合在一起的讨论很棒.但总之,它们就像这样(左边是典型的小部件,中间是'元素',右边是'渲染框'):
问题是:当你实例化一个新的小部件时; 扑动将它与旧的比较.重用它的"元素",它指向一个RenderBox.并改变 renderbox属性.
哦,但这是如何回答我的问题的呢?
嗯,这很容易.实例化一个InheritedWidget,然后调用context.inheritedWidgetOfExactType(或者MyClass.of基本相同); 隐含的是它会倾听Element与你相关联的内容InheritedWidget.每当它Element获得一个新的小部件时,它将强制刷新调用前一个方法的任何小部件.
简而言之,当你InheritedWidget用一个全新的替换现有的; 颤动会看到它改变了.并且会通知可能修改的绑定小部件.
如果你理解了一切,你应该已经猜到了解决方案:
将InheritedWidget内部包裹起来StatefulWidget,InheritedWidget只要有变化,就会创造一个全新的!在这种情况下,建议您的Element数据实际上只是您的实例,context.inheritedWidgetOfExactType然后MyClass.of私有.避免不必要的复制粘贴和可能的错误.
实际代码的最终结果是:
class MyInherited extends StatefulWidget {
static MyInheritedData of(BuildContext context) =>
context.inheritFromWidgetOfExactType(MyInheritedData) as MyInheritedData;
const MyInherited({Key key, this.child}) : super(key: key);
final Widget child;
@override
_MyInheritedState createState() => _MyInheritedState();
}
class _MyInheritedState extends State<MyInherited> {
String myField;
void onMyFieldChange(String newValue) {
setState(() {
myField = newValue;
});
}
@override
Widget build(BuildContext context) {
return MyInheritedData(
myField: myField,
onMyFieldChange: onMyFieldChange,
child: widget.child,
);
}
}
class MyInheritedData extends InheritedWidget {
final String myField;
final ValueChanged<String> onMyFieldChange;
MyInheritedData({
Key key,
this.myField,
this.onMyFieldChange,
Widget child,
}) : super(key: key, child: child);
@override
bool updateShouldNotify(MyInheritedData oldWidget) {
return oldWidget.myField != myField ||
oldWidget.onMyFieldChange != onMyFieldChange;
}
}
Run Code Online (Sandbox Code Playgroud)
但是不会创建一个新的InheritedWidget重建整个树?
不,它不会是必然的.因为您的新InheritedWidget可能具有与以前完全相同的子级.确切地说,我的意思是同一个例子.具有相同实例的小部件不会重建.
在大多数情况下(在应用程序的根目录中有一个inheritedWidget),继承的小部件是不变的.所以没有不必要的重建.
mak*_*imr 16
TL; DR
创建小部件时,请勿在updateShouldNotify方法内使用大量计算,而应使用const而不是 new
首先,我们应该了解什么是Widget,Element和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;
}
}
Run Code Online (Sandbox Code Playgroud)
InheritedWidget-只是在我们的示例中实现一种重要方法updateDhouldNotify的小部件。 updateShouldNotify-一个接受一个参数oldWidget并返回一个布尔值的函数:true或false。
像任何小部件一样,InheritedWidget也具有相应的Element对象。它是InheritedElement。每次我们构建一个新的小部件时,InheritedElement 都会在小部件上调用updateShouldNotify(在祖先上调用setState)。当updateShouldNotify返回true时, InheritedElement遍历依赖项(?),并对其调用方法didChangeDependencies。
InheritedElement在哪里获得依赖关系?在这里我们应该看一下InheritedFromWidgetOfExactType方法。
InheritFromInheritOfExactType-在BuildContext中定义的此方法, 每个元素都实现BuildContext接口(Element == BuildContext)。因此,每个元素都有此方法。
让我们看一下InheritFromFromWidgetOfExactType的代码:
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
Run Code Online (Sandbox Code Playgroud)
在这里,我们尝试在按类型映射的_inheritedWidgets中找到祖先。如果找到祖先,那么我们将调用InheritFromElement。
继承代码的代码:
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
Run Code Online (Sandbox Code Playgroud)
因此,现在我们知道InheritedElement从何处获取依赖项。
现在让我们看一下didChangeDependencies方法。每个元素都有此方法:
void didChangeDependencies() {
assert(_active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}
Run Code Online (Sandbox Code Playgroud)
如我们所见,此方法只是将一个元素标记为脏元素,并且应在下一帧重建该元素。重建意味着调用方法建立在coresponding小部件元素上。
但是,“当我重建InheritedWidget时,整个子树都会重建吗?”呢?在这里,我们应该记住,小部件是不可变的,如果您创建新的小部件,Flutter将重建子树。我们该如何解决?
来自文档:
[BuildContext.dependOnInheritedWidgetOfExactType] 获取给定类型的最近的小部件,该类型必须是具体 InheritedWidget 子类的类型,并将此构建上下文注册到该小部件,以便当该小部件发生更改(或引入该类型的新小部件时,或者小部件消失),此构建上下文将被重建,以便它可以从该小部件获取新值。
这通常是从 of() 静态方法隐式调用的,例如 Theme.of。
正如OP所指出的,InheritedWidget实例不会改变......但它可以被小部件树中同一位置的新实例替换。发生这种情况时,可能需要重建已注册的小部件。该InheritedWidget.updateShouldNotify方法做出此确定。(参见:文档)
那么如何替换实例呢?实例InheritedWidget可以包含在 中StatefulWidget,它可以用新实例替换旧实例。
| 归档时间: |
|
| 查看次数: |
24548 次 |
| 最近记录: |