颤动: 如何正确使用继承的小部件?

什么是正确的使用方法继承小部件?到目前为止,我理解它为您提供了沿着 Widget 树传播数据的机会。在极端情况下,如果你把它放在 RootWidget 中,它将可以从所有路由树中的所有 Widget 访问,这很好,因为我必须让我的视图模型/模型可访问我的 Widget,而不必求助于全局或单例。

但继承的 Widget 是不可变的,那么我如何更新它呢?更重要的是,我的有状态部件是如何被触发来重建它们的子树的?

不幸的是,这里的文档非常不清楚,经过大量讨论,似乎没有人真正知道使用它的正确方法。

我引用 Brian Egan 的一句话:

是的,我认为这是一种向下传播数据的方法 来自 API 文档的令人困惑的信息:

”以这种方式引用继承的小部件时,将导致 当继承的小部件本身改变状态时,消费者可以重新构建。”

当我第一次读到这个的时候,我想:

我可以将一些数据填充到 Heritage itedWidget 中,然后再对其进行变异。 当变异发生的时候,它会重建所有的小部件 参考我的继承小部件我发现:

为了改变一个 Heritage itedWidget 的状态,您需要换行 然后实际改变状态 并将此数据传递给 将数据传递给它的所有子级。然而,在这种情况下,它 似乎可以重新构建 StateftionWidget 下面的整个树,而不是 仅仅是引用 Heritage itedWidget 的 Widgets,对吗? 或者它知道如何跳过引用 如果 updateShouldNotify 返回 false,继承 Widget?

71706 次浏览

问题来自你的报价,这是不正确的。

正如您所说的,与其他小部件一样,Heritage itedWidgets 是不可变的。因此它们不是 更新。它们是重新创建的。

问题是: Heritage itedWidget 只是一个简单的小部件,除了保存数据什么也不做。它没有任何更新逻辑或任何东西。 但是,与其他小部件一样,它与 Element相关联。 你猜怎么着? 这个东西是可变的,颤振将重用它,只要有可能!

修正后的引文如下:

如果使用这种方式进行引用,则在与 继承的元素更改关联的 Heritage itedWidget 发生变化时,使用者将重新生成。

关于如何将 widgets/element/renderbox 连接在一起有一个很好的讨论。 但简而言之,它们是这样的(左边是典型的小部件,中间是“元素”,右边是“渲染框”) :

enter image description here

问题是: 当您实例化一个新的小部件时,flutter 会将其与旧的小部件进行比较。重用它的“ Element”,它指向一个 RenderBox。和 变异的 RenderBox 属性。


好吧,但这怎么回答我的问题呢?

当实例化一个 Heritage itedWidget,然后调用 context.inheritedWidgetOfExactType(或者基本相同的 MyClass.of)时; 这意味着它将监听与 InheritedWidget相关联的 Element。每当 Element得到一个新的小部件时,它将强制刷新调用前一个方法的任何小部件。

简而言之,当您用一个全新的 InheritedWidget替换现有的 InheritedWidget时,flutter 将看到它发生了变化。并将潜在的修改通知绑定的小部件。

如果你什么都懂,你应该已经猜到答案了:

将你的 InheritedWidget包裹在一个 StatefulWidget内,这将创建一个全新的 InheritedWidget,无论什么时候发生变化!

实际代码的最终结果将是:

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);


static MyInheritedData of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedData>();
}


@override
bool updateShouldNotify(MyInheritedData oldWidget) {
return oldWidget.myField != myField ||
oldWidget.onMyFieldChange != onMyFieldChange;
}
}

但是创建一个新的 Heritage itedWidget 不会重新构建整个树吗?

不,不一定。因为您的新 Heritage itedWidget 可能具有与以前完全相同的子级。我指的是同一个例子。 具有与之前相同实例的小部件不会重新构建。

在大多数情况下(在应用程序的根目录中有一个继承的小部件) ,继承的小部件是 不变。所以没有不必要的重建。

DR

不要在 UpdateShouldNotify 更新应该通知方法中使用繁重的计算,在创建小部件时使用 康斯特而不是 新的


首先,我们应该理解什么是 Widget、 Element 和 Render 对象。

  1. 渲染 对象实际上是在屏幕上渲染的。它们是 易变的,包含绘制和布局逻辑。渲染树非常类似于 Web 中的文档对象模型(Document Object Model,DOM) ,您可以将渲染对象看作该树中的 DOM 节点
  2. Widget -是对应该呈现的内容的描述。他们是 永恒不变和便宜。因此,如果一个 Widget 回答“什么?”(声明性方法)然后一个 Render 对象回答问题“如何?”(命令式进场)。网络上的一个类比是“虚拟 DOM”。
  3. Element/BuildContext -是 小部件渲染对象之间的代理。它包含有关小部件在树 * 中的位置以及当相应的小部件更改时如何更新 Render 对象的信息。

现在我们准备深入研究 继承的部件和 BuildContext 的方法 精确类型

作为一个例子,我建议我们考虑下面这个例子,它来自 Flutter 的关于 Heritage itedWidget 的文档:

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;
}
}

就是一个小部件,在我们的例子中实现了一个重要的方法-UpdateShouldNotify 更新应该通知UpdateShouldNotify -接受一个参数 老家伙并返回一个布尔值: true 或 false 的函数。

像任何小部件一样,继承的部件有一个对应的 Element 对象。是 继承的元素。每当我们构建一个新的小部件时(在祖先上调用 SetState) ,都会对小部件调用 UpdateShouldNotify 更新应该通知。当 UpdateShouldNotify 更新应该通知返回 没错继承元素时,它会遍历 依赖关系(?)并在其上调用方法 依赖性

从哪里继承元素得到 依赖关系? 在这里我们应该看看 精确类型方法。

Heritage itFromWidgetOfExactType -在 BuildContext 和 每个 Element 实现 BuildContext 接口(Element = = BuildContext)。

让我们来看看 Heritage itFromWidgetOfExactType 的代码:

final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}

在这里,我们试图找到 继承的小部件中按类型映射的祖先。 如果找到了祖先,我们就调用 继承 FromElement

继承 FromElement的代码是:

  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
  1. 我们添加祖先作为当前元素(_ depencies.add (祖先))的依赖项
  2. 我们将当前元素添加到祖先的依赖关系中(anceor.updateDependency (this,方面))
  3. 我们返回祖先的小部件作为 精确类型(返回祖先的小部件)的结果

所以现在我们知道了 Heritage itedElement 从哪里获得它的依赖项。

现在让我们看看 依赖性方法。 每个元素都有这个方法:

  void didChangeDependencies() {
assert(_active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}

正如我们所看到的,这个方法只是将一个元素标记为 肮脏,这个元素应该在下一帧中重建。重建意味着对相应的小部件元素调用方法 建造

但是“在重新构建继承的 Widget 时重新构建整个子树?”。 在这里我们应该记住,小部件是不可变的,如果你创建新的小部件 Flutter 将重建子树。我们该怎么补救?

  1. 手动缓存小部件
  2. 使用 康斯特,因为 const 创建唯一的一个实例的值/类

来自 医生:

[ BuildContext.depOnHeritage itedWidgetOfExactType ]获取最近的小部件 指定类型,该类型必须是混凝土的类型 继承的 Widget 子类,并用该子类注册此构建上下文 这样,当这个小部件发生变化时(或者这个小部件的一个新部件) 类型,或者小部件消失) ,这个构建上下文是 重新构建,以便可以从该小部件获取新值。

这通常从()静态方法隐式调用,例如。 主题。

正如 OP 指出的那样,InheritedWidget实例不会改变... 但是可以在小部件树的同一位置用新实例替换它。当这种情况发生时,可能需要重新构建已注册的小部件。InheritedWidget.updateShouldNotify方法做出了这个判断。(见: 医生)

那么如何替换实例呢?InheritedWidget实例可以包含在 StatefulWidget中,StatefulWidget可以用新实例替换旧实例。

Heritage itedWidget 管理应用程序的集中数据并将其传递给子程序,就像我们可以在这里存储 给你解释的购物车数量一样: