ChatGPT解决这个技术问题 Extra ChatGPT

Flutter:如何正确使用 Inherited Widget?

使用 InheritedWidget 的正确方法是什么?到目前为止,我了解到它使您有机会将数据沿 Widget 树传播。在极端情况下,如果您将其设置为 RootWidget,它将可以从所有路由上的树中的所有 Widget 访问,这很好,因为我必须以某种方式使我的 ViewModel/Model 可用于我的 Widget,而不必求助于全局变量或单例。

但是 InheritedWidget 是不可变的,那么我该如何更新它呢?更重要的是,我的 Stateful Widgets 是如何被触发来重建它们的子树的?

不幸的是,这里的文档非常不清楚,在与很多人讨论之后,似乎没有人真正知道使用它的正确方法是什么。

我添加了 Brian Egan 的一句话:

是的,我认为它是一种沿树传播数据的方式。我发现令人困惑的是,来自 API 文档:“继承的小部件,当以这种方式引用时,将导致消费者在继承的小部件本身改变状态时重建。”当我第一次读到这篇文章时,我想:我可以在 InheritedWidget 中填充一些数据,然后再对其进行变异。当这种突变发生时,它将重建引用我的 InheritedWidget 的所有小部件 我发现:为了改变 InheritedWidget 的状态,您需要将其包装在 StatefulWidget 中然后您实际上改变 StatefulWidget 的状态并传递此数据下到 InheritedWidget,它将数据传递给它的所有子级。但是,在这种情况下,它似乎重建了 StatefulWidget 下的整个树,而不仅仅是引用 InheritedWidget 的 Widget。那是对的吗?或者,如果 updateShouldNotify 返回 false,它是否会以某种方式知道如何跳过引用 InheritedWidget 的 Widget?

好问题!谢谢你的提问。

S
Shubhamhackz

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

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

问题是:InheritedWidget 只是一个简单的小部件,除了保存数据之外什么都不做。它没有任何更新或任何逻辑。但是,与任何其他小部件一样,它与 Element 相关联。你猜怎么着?这个东西是可变的,flutter 会尽可能地重用它!

更正后的报价是:

InheritedWidget,当以这种方式引用时,将导致消费者在与 InheritedElement 关联的 InheritedWidget 发生更改时重建。

There's a great talk about how widgets/elements/renderbox are pluged together。但简而言之,它们是这样的(左边是您的典型小部件,中间是“元素”,右边是“渲染框”):

https://i.stack.imgur.com/ZhPdS.png

事情是:当你实例化一个新的小部件时;颤振会将其与旧的进行比较。重用它的“元素”,它指向一个渲染框。并改变 RenderBox 属性。

好的,但这如何回答我的问题?

当实例化一个 InheritedWidget,然后调用 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;
  }
}

但是创建一个新的 InheritedWidget 不会重建整个树吗?

不,不一定。因为您的新 InheritedWidget 可能具有与以前完全相同的孩子。确切地说,我的意思是同一个例子。具有与以前相同的实例的小部件不会重建。

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


但是创建一个新的 InheritedWidget 不会重建整个树吗?那么为什么需要监听器呢?
对于您的第一条评论,我在答案中添加了第三部分。至于乏味:我不同意。一个代码片段可以很容易地生成这个。访问数据就像调用 MyInherited.of(context) 一样简单。
不确定您是否感兴趣,但使用此技术更新了示例:github.com/brianegan/flutter_architecture_samples/tree/master/… 现在肯定会减少一些重复!如果您对该实现有任何其他建议,如果您有空闲时间,会喜欢代码审查 :) 仍在尝试找出共享此逻辑跨平台(Flutter 和 Web)并确保其可测试的最佳方式(特别是异步的东西)。
由于 updateShouldNotify 测试总是引用同一个 MyInheritedState 实例,它不总是返回 false 吗?当然 MyInheritedStatebuild 方法是创建新的 _MyInherited 实例,但 data 字段总是引用 this no?我遇到了问题...如果我只是硬编码 true 就可以了。
@cdock 是的,我的错。不记得为什么我这样做了,因为它显然行不通。通过编辑修复为true,谢谢。
T
TheIT

TL;博士

不要在 updateShouldNotify 方法中使用繁重的计算,并在创建小部件时使用 const 而不是 new

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

渲染对象是实际在屏幕上渲染的对象。它们是可变的,包含绘画和布局逻辑。渲染树与 Web 中的文档对象模型 (DOM) 非常相似,您可以将渲染对象视为此树中的 DOM 节点 Widget - 是对应该渲染的内容的描述。它们是不可变的且便宜的。因此,如果 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 都会在小部件上调用 updateShouldNotify(在祖先上调用 setState)。当 updateShouldNotify 返回 true 时,InheritedElement 会遍历 dependencies(?) 并在其上调用方法 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 创建唯一的值/类实例


很好的解释 maksimr。最让我困惑的是,如果在继承的Widget 被替换时,整个子树还是重建了,那么 updateShouldNotify() 的意义何在?
所以这里继承的小部件可以在值发生变化时更新它的侦听器,而这正是提供者小部件所做的,它们之间有什么区别。 。如我错了请纠正我
清理在哪里进行?即删除小部件时从HashSet 中删除所有依赖项?
D
DarkNeuron

docs

[BuildContext.dependOnInheritedWidgetOfExactType] 获取给定类型的最近小部件,该类型必须是具体 InheritedWidget 子类的类型,并将此构建上下文注册到该小部件,以便当该小部件更改(或引入该类型的新小部件时,或小部件消失),此构建上下文将被重建,以便它可以从该小部件获取新值。这通常从 of() 静态方法中隐式调用,例如 Theme.of。

正如 OP 所指出的,InheritedWidget 实例不会改变......但它可以用小部件树中相同位置的新实例替换。发生这种情况时,可能需要重建已注册的小部件。 InheritedWidget.updateShouldNotify 方法进行此确定。 (见:docs

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


S
Sunil

InheritedWidget 管理应用程序的集中数据并将其传递给孩子,就像我们可以在这里存储购物车计数一样,如 here 所述:


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅