InheritedWidget 混淆

Posted

技术标签:

【中文标题】InheritedWidget 混淆【英文标题】:InheritedWidget confusion 【发布时间】:2019-06-26 21:50:38 【问题描述】:

颤振documentation for InheritedWidget 说

小部件的基类,可有效地沿树向下传播信息。

要从构建上下文中获取特定类型的继承小部件的最近实例,请使用 BuildContext.inheritFromWidgetOfExactType。

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

鉴于 Flutter 中的小部件是不可变的,并且在示例代码中......

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) => color != old.color;

颜色属性为final,因此无法重新分配。假设这个小部件就在树的顶部,就像在大多数示例中一样,它什么时候会有用。对于要替换的小部件,必须创建一个新实例。

大概在这样做的地方,将创建一个作为孩子传递的新实例,导致该孩子的后代也重建,创建其孩子的新实例等。

最终还是要重建整棵树。那么使用inheritFromWidgetOfExactType进行的选择性更新是没有意义的,当InheritedWidget的一个实例的数据对于那个实例永远不会改变的时候?

编辑:

这是我不明白的最简单的例子,我可以放在一起。 在此示例中,“更改”位于应用程序根附近的 InheritedWidget/FrogColor 的唯一方法是重建其父级 (MyApp)。这会导致它重建其子代并创建FrogColor 的新实例,并传递一个新的子实例。我没有看到InheritedWidget/FrogColor 的任何其他方式 会像文档中那样改变它的状态

...当继承的小部件本身改变状态时,将导致消费者重建。

import 'package:flutter/material.dart';
import 'dart:math';

void main() 

  runApp(MyApp());


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) => color != old.color;


class MyApp extends StatefulWidget 
  // This widget is the root of your application.

  MyAppState createState() => MyAppState();


class MyAppState extends State<MyApp>

  @override
  Widget build(BuildContext context) 
    var random = Random(DateTime.now().millisecondsSinceEpoch);

    return FrogColor(
        color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
        child:MaterialApp(
            title: 'Flutter Demo',
            home: Column (
                children: <Widget>[
                  WidgetA(),
                  Widget1(),
                  FlatButton(
                      child:Text("set state",style:TextStyle(color:Colors.white)),
                      onPressed:() => this.setState(())
                  )
                ]
            )
        )
    );
  


class WidgetA extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    print("Ran Build $this.runtimeType.toString()");
    return  WidgetB();
  

class WidgetB extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    print("Ran Build $this.runtimeType.toString()");
    return  Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
  

class Widget1 extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    print("Ran Build $this.runtimeType.toString()");
    return  Widget2();
  

class Widget2 extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    print("Ran Build $this.runtimeType.toString()");
    return  Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
  

此外,this的输出是

I/flutter (24881): Ran Build WidgetA
I/flutter (24881): Ran Build WidgetB
I/flutter (24881): Ran Build Widget1
I/flutter (24881): Ran Build Widget2

所以所有子小部件总是被重建。使在inheritFromWidgetOfExactType中完成的注册也毫无意义。

编辑2:

响应 cmets 中的@RémiRousselet 回答,修改上面的示例,类似于

class MyAppState extends State<MyApp>

  Widget child;

  MyAppState()
  
    child = MaterialApp(
        title: 'Flutter Demo',
        home: Column (
            children: <Widget>[
              WidgetA(),
              Widget1(),
              FlatButton(
                  child:Text("set state",style:TextStyle(color:Colors.white)),
                  onPressed:() => this.setState(())
              )
            ]
        )
    );
  

  @override
  Widget build(BuildContext context) 
    var random = Random(DateTime.now().millisecondsSinceEpoch);
    return FrogColor(
        color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
        child: child
    );
  

通过存储不应在构建函数之外修改的树来工作,以便在每次重建时将相同的子树传递给 InhertedWidget。这确实有效,只会导致已向 inheritFromWidgetOfExactType 注册的小部件的重建得到重建,而不是其他小部件。

虽然@RémiRousselet 说将子树存储为状态的一部分是不正确的,但我不认为有任何理由认为这是不正确的,事实上他们在一些谷歌教程视频中这样做了。 Here 她创建了一个子树并作为状态的一部分保存。在她的案例中,有 2 个 StatelessColorfulTile() 小部件。

【问题讨论】:

另见scoped_model,这也是自定义InheritedWidget,没有完整的样板代码 Flutter: How to correctly use an Inherited Widget?的可能重复 @George 这个其他答案并没有真正深入我所问的问题。 快速错误说明:n = nextInt(max) 返回 0 api.flutter.dev/flutter/dart-math/Random/nextInt.html 【参考方案1】:

大概在这样做的地方,将创建一个作为孩子传递的新实例,导致该孩子的后代也重建,创建其孩子的新实例等。

最终还是要重建整棵树。

这就是你的困惑来自哪里

小部件重建不会强制其后代重建。

当父级重建时,框架会在内部检查newChild == oldChild,在这种情况下,子级没有重建。

因此,如果一个小部件的实例没有改变,或者如果它覆盖 operator== 那么当它的父级更新时,一个小部件可能不会重建。

这也是AnimatedBuilder提供child属性的原因之一:

AnimatedBuilder(
  animation: animation,
  builder: (context, child) 
    return Container(child: child,);
  ,
  child: Text('Hello world'),
);

这可确保在整个动画期间,child 被保留,因此不会重建。带来更优化的 UI。

【讨论】:

谢谢@RémiRousselet - 我已经用我没有得到的样本更新了我的问题。我想我不明白的是 InheritedWidget 在树顶时会发生变化的条件。好像这只是在创建一个新实例时(因为它的不变性),那么由于这通常是一个根构建函数,在那里创建的所有其他小部件也会被重新实例化,从而导致它们的重建。 我想我能想到的唯一一次不会重建孩子的是 StatefulWidget 将其子树作为其状态的成员,并在其构建函数中返回它? ..这是正确的吗? 不正确。查看AnimatedBuilder 示例。现在认为AnimatedBuilder 是你的StatefulWidgetContainer 是你的InheritedWidget。这就是你应该如何改变InheritedWidgets。 太棒了,我看到你的例子是如何工作的。我已经对我粘贴的示例进行了修改,从而编辑了我的问题。尽管这将子树存储为状态的成员,但这确实意味着它是在构建函数之外创建的,因此除非父 MyApp 被重建并创建一个新状态,否则它不会被重建。但现在只允许重建依赖于 InheritedWidget 更改的子级。我假设像这样将子树存储为状态的成员是可以的。 ... 不。就像这里所做的那样***.com/questions/49491860/…

以上是关于InheritedWidget 混淆的主要内容,如果未能解决你的问题,请参考以下文章

flutter Provider状态管理

为啥我需要 Flutter 中的 InheritedWidget

提供者与 InheritedWidget

一文搞懂InheritedWidget局部刷新机制

Flutter 数据共享 InheritedWidget

inheritFromWidgetOfExactType 不适用于通用 InheritedWidget