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

Tho*_*mas 74 dart flutter

使用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),继承的小部件是不变的.所以没有不必要的重建.

  • 由于`updateShouldNotify`测试始终引用相同的`MyInheritedState`实例,它是否总是返回`false`?当然`MyInheritedState`的`build`方法正在创建新的`_MyInherited`实例,但是`data`字段总是引用`this` no?我有问题...如果我只是硬编码`true`就可以工作. (4认同)
  • 但是创建一个新的 InheritedWidget 不会重建整个树吗?那么为什么需要听众呢? (3认同)
  • 不确定您是否感兴趣,但是使用这种技术更新了样本:https://github.com/brianegan/flutter_architecture_samples/tree/master/example/inherited_widget现在肯定会少一点重复!如果您对该实现有任何其他建议,如果您有一些时间可以考虑,那么您会喜欢代码审查:)仍然试图找出分享这个逻辑交叉平台(Flutter和Web)的最佳方式,并确保它是可测试的(特别是异步的东西). (3认同)
  • 对于您的第一条评论,我在答案中添加了第三部分。至于乏味:我不同意。代码片段可以很容易地生成它。访问数据就像调用“MyInherited.of(context)”一样简单。 (2认同)
  • 我在这里什么也没发明。在检查了 Flutter 的代码(尤其是 Theme/Navigator)之后,我意识到这就是他们使用 InheritedWidget 的方式。它不仅避免了重复。但它也允许“扩展”继承的Widget。例如,`Theme` 扩展 `IconTheme` 而不破坏 `IconTheme.of(context)` ! (2认同)
  • @cdock是的,我的坏.不记得我为什么这样做,因为它显然不起作用.通过编辑修复为true,谢谢. (2认同)

mak*_*imr 16

TL; DR

创建小部件时,请勿在updateShouldNotify方法内使用大量计算,而应使用const而不是 new


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

  1. 渲染对象是屏幕上实际渲染的对象。它们是可变的,包含绘画和布局逻辑。渲染树与网络中的文档对象模型(DOM)非常相似,您可以在此树中将渲染对象视为DOM节点
  2. 小部件 -是应呈现内容的描述。它们是一成不变的,便宜的。因此,如果Widget回答问题“ What?”(声明性方法),则Render对象回答问题“ How?”(命令性方法)。网络上的类比是“虚拟DOM”。
  3. Element / BuildContext-WidgetRender对象之间的代理。它包含有关小部件在tree *中的位置以及更改相应小部件时如何更新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)
  1. 我们将祖先添加为当前元素的依赖项(_dependencies.add(ancestor))
  2. 我们将当前元素添加到祖先的依赖项中(ancestor.updateDependencies(this,Aspect))
  3. 我们返回InheritanceFromWidgetOfExactType的结果作为祖先的小部件(返回ancestor.widget)

因此,现在我们知道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将重建子树。我们该如何解决?

  1. 手动缓存小部件(手动)
  2. 使用const是因为const 仅创建 value / class的一个实例

  • 马克西姆先生的很好的解释。最让我困惑的是,如果当inheritedWidget被替换时整个子树都会重建,那么updateShouldNotify()的意义是什么? (2认同)

kku*_*ian 5

来自文档

[BuildContext.dependOnInheritedWidgetOfExactType] 获取给定类型的最近的小部件,该类型必须是具体 InheritedWidget 子类的类型,并将此构建上下文注册到该小部件,以便当该小部件发生更改(或引入该类型的新小部件时,或者小部件消失),此构建上下文将被重建,以便它可以从该小部件获取新值。

这通常是从 of() 静态方法隐式调用的,例如 Theme.of。

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

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