使用InheritedWidget的正确方法是什么?到目前为止,我明白它让你有机会沿着Widget树传播数据。在极端情况下如果你把它作为RootWidget,它将可以从所有Routes树中的所有Widgets访问,这很好,因为不知何故我必须使我的ViewModel / Model可以访问我的Widgets而不必求助于全局或Singletons。
但是InheritedWidget是不可变的,那么如何更新呢?更重要的是,我的状态窗口小部件如何触发重建其子树?
不幸的是,文档在这里非常不清楚,经过大量讨论后,似乎没有人真正知道使用它的正确方法。
我添加了Brian Egan的一句话:
是的,我认为这是一种沿树传播数据的方法。我发现了什么 困惑,来自API文档:
"当以这种方式引用时,继承的小部件将导致 当继承的窗口小部件本身改变状态时,消费者重建。"
当我第一次阅读本文时,我想:
我可以在InheritedWidget中填充一些数据并稍后进行修改。 当突变发生时,它将重建所有的小部件 引用我的InheritedWidget我发现了什么:
为了改变InheritedWidget的状态,你需要换行 它在StatefulWidget中你实际上改变了状态 StatefulWidget并将此数据传递给InheritedWidget,后者 将数据交给所有孩子。但是,在那种情况下,它 似乎重建了StatefulWidget下面的整个树,而不是 只是引用InheritedWidget的Widgets。那是对的吗? 或者它会以某种方式知道如何跳过引用它的小部件 如果updateShouldNotify返回false,则为InheritedWidget吗?
答案 0 :(得分:56)
问题来自你的引用,这是不正确的。
正如你所说,InheritedWidgets和其他小部件一样,是不可变的。因此,他们不会更新。它们是重新创造的。
事情是: InheritedWidget只是一个简单的小部件,只能保存数据。它没有任何更新逻辑或任何。
但是,与任何其他小部件一样,它与Element
相关联。
你猜怎么着 ?这个东西是可变的,只要有可能就会重复使用它!
更正后的报价为:
当以这种方式引用时,InheritedWidget将导致使用者在与 InheritedElement 关联的InheritedWidget更改时重建。
There's a great talk about how widgets/elements/renderbox are pluged together。 但简而言之,它们就像这样(左边是典型的小部件,中间是'元素',右边是'渲染框'):
事情是:当你实例化一个新的小部件时;扑动将它与旧的比较。重用它的“元素”,它指向一个RenderBox。 mutate renderbox属性。
Okey,但这是如何回答我的问题的呢?
嗯,这很简单。实例化一个InheritedWidget,然后调用context.inheritedWidgetOfExactType
(或基本相同的MyClass.of
);隐含的是它会听取与您Element
相关联的InheritedWidget
。每当Element
获得一个新的小部件时,它将强制刷新调用前一个方法的任何小部件。
简而言之,当您用全新的InheritedWidget
替换现有的InheritedWidget
时;颤动会看到它改变了。并且会通知可能修改的绑定小部件。
如果你理解了一切,你应该已经猜到了解决方案:
将StatefulWidget
包裹在InheritedWidget
内,只要有变化,就会创建一个全新的InheritedWidget
!
在这种情况下,建议您的StatefulWidget
数据实际上只是InheritedWidget
的实例,然后将class MyInherited extends StatefulWidget {
Widget child;
MyInherited({this.child});
@override
MyInheritedState createState() => new MyInheritedState();
static MyInheritedState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
}
class MyInheritedState extends State<MyInherited> {
String _myField;
// only expose a getter to prevent bad usage
String get myField => _myField;
void onMyFieldChange(String newValue) {
setState(() {
_myField = newValue;
});
}
@override
Widget build(BuildContext context) {
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
/// Only has MyInheritedState as field.
class _MyInherited extends InheritedWidget {
final MyInheritedState data;
_MyInherited({Key key, this.data, Widget child}) : super(key: key, child: child);
@override
bool updateShouldNotify(_MyInherited old) {
return true;
}
}
设为私有。避免不必要的复制粘贴和可能的错误。
实际代码的最终结果是:
class Program
{
static DataTable Product = new DataTable("Product");
static DataTable OrderHistory = new DataTable("OrderHeader");
static DataTable OrderLines = new DataTable("OrderLines");
static DataTable CustomerHistory = new DataTable("CustomerHistory");
public static void LoadTables()
{
Product.Columns.AddRange(new DataColumn[]
{
new DataColumn("ProductID",typeof(int)),
new DataColumn("ProductCode",typeof(int)),
new DataColumn("Rank",typeof(int))
});
Product.Rows.Add(200001, 10001, 1);
Product.Rows.Add(200002, 10002, 2);
Product.Rows.Add(200003, 10003, 3);
Product.Rows.Add(200004, 10004, 4);
Product.Rows.Add(200005, 10005, 5);
Product.Rows.Add(200006, 10006, 6);
Product.Rows.Add(200007, 10007, 7);
Product.Rows.Add(200008, 10008, 8);
OrderHistory.Columns.AddRange(new DataColumn[]
{
new DataColumn("CustomerID", typeof(string)),
new DataColumn("OrderID", typeof(int))
});
OrderHistory.Rows.Add("ABC001", 123);
OrderHistory.Rows.Add("ABC002", 124);
OrderHistory.Rows.Add("ABC003", 125);
OrderHistory.Rows.Add("ABC004", 126);
//OrderHistory.Rows.Add("BA009", 127);
OrderLines.Columns.AddRange(new DataColumn[]
{
new DataColumn("OrderID",typeof(int)),
new DataColumn("ProductID", typeof(int))
});
OrderLines.Rows.Add(123, 200001);
OrderLines.Rows.Add(123, 200002);
OrderLines.Rows.Add(123, 200003);
OrderLines.Rows.Add(123, 200004);
OrderLines.Rows.Add(123, 200005);
OrderLines.Rows.Add(124, 200006);
OrderLines.Rows.Add(124, 200006);
OrderLines.Rows.Add(124, 200007);
OrderLines.Rows.Add(124, 200008);
OrderLines.Rows.Add(124, 200009);
OrderLines.Rows.Add(127, 200001);
OrderLines.Rows.Add(127, 200002);
OrderLines.Rows.Add(127, 200009);
OrderLines.Rows.Add(127, 200008);
OrderLines.Rows.Add(127, 200007);
OrderLines.Rows.Add(126, 200006);
OrderLines.Rows.Add(126, 200005);
OrderLines.Rows.Add(126, 200008);
}
static void Main(string[] args)
{
LoadTables();
var rows = (from orderLines in OrderLines.AsEnumerable()
join orderHistory in OrderHistory.AsEnumerable()
on orderLines["OrderID"] equals orderHistory["OrderID"]
select new
{
CustomerID = orderHistory["CustomerID"],
ProductID = orderLines["ProductID"]
}).Distinct();
CustomerHistory.Columns.AddRange(new DataColumn[]
{
new DataColumn("CustomerID"),
new DataColumn("ProductID")
});
foreach (var row in rows)
{
CustomerHistory.Rows.Add(row.CustomerID, row.ProductID);
}
//foreach (DataRow row in CustomerHistory.Rows)
//{
// Console.WriteLine(row[0].ToString() + "\t" + row[1].ToString());
//}
var topSellers = "Select Top250 CustomerHistory.CustomerID, Product.ProductCode";
}
}
但是不会创建一个新的InheritedWidget重建整个树吗?
不,它不会是必然的。因为您的新InheritedWidget可能具有与以前完全相同的子级。确切地说,我的意思是同一个例子。 具有相同实例的小部件不会重建。
在大多数情况下(在应用程序的根目录中有一个inheritedWidget),继承的小部件是常量。所以没有不必要的重建。
答案 1 :(得分:8)
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;
}
}
InheritedWidget-仅是在我们的示例中实现一种重要方法- updateShouldNotify 的小部件。 updateShouldNotify -一个接受一个参数 oldWidget 并返回布尔值:true或false的函数。
与任何小部件一样, InheritedWidget 具有对应的Element对象。它是 InheritedElement 。每次我们构建新的小部件时,InheritedElement都会在小部件上调用 updateShouldNotify (在祖先上调用 setState )。当 updateShouldNotify 返回 true 时,InheritedElement遍历 dependencies (?)并对其调用方法 didChangeDependencies 。
InheritedElement在哪里获得依赖?在这里,我们应该看看 inheritFromWidgetOfExactType 方法。
inheritFromWidgetOfExactType -在BuildContext和 每个元素实现BuildContext接口(Element == BuildContext)。因此,每个元素都有此方法。
让我们看一下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;
}
所以现在我们知道InheritedElement从哪里获取依赖项。
现在让我们来看一下 didChangeDependencies 方法。 每个元素都有此方法:
void didChangeDependencies() {
assert(_active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}
我们可以看到此方法只是将元素标记为 dirty ,并且应在下一帧重建该元素。 Rebuild 表示在核心响应小部件元素上调用方法 build 。
但是“重新构建InheritedWidget时整个子树是否重新构建?”呢? 在这里,我们应该记住,小部件是不可变的,如果您创建新的小部件,Flutter将重建子树。我们该如何解决?
答案 2 :(得分:1)
来自docs:
[BuildContext.inheritFromWidgetOfExactType]获取最近的窗口小部件 给定类型的,必须是混凝土的类型 InheritedWidget子类,并向其注册该构建上下文 小部件,以便当该小部件更改时(或该小部件的新小部件 类型被引入,或者小部件消失了),这个构建上下文是 重新构建,以便它可以从该小部件获取新值。
这通常是从of()静态方法中隐式调用的,例如 主题
正如OP所指出的,InheritedWidget
实例不会更改...但是可以用小部件树中相同位置的新实例替换它。发生这种情况时,可能需要重建已注册的窗口小部件。 InheritedWidget.updateShouldNotify
方法进行此确定。 (请参阅:docs)
那么如何替换实例? InheritedWidget
可以包含一个StatefulWidget
实例,该实例可以用新实例替换旧实例。
答案 3 :(得分:0)
InheritedWidget管理应用程序的集中式数据并将其传递给子级,就像我们可以在此处存储购物车计数一样,如here所述: