在initState或类构造函数中定义小部件,而不是在构建?

时间:2018-10-17 13:45:32

标签: flutter

我看到的大多数示例和普遍的智慧都为类中的Widget使用了一个典型的模板,其中所有其他的Widget都在build方法内创建(我认为可以使代码更清晰,但是可以设置除了此)。

但是,如果引用的窗口小部件不做进一步更改,则每次构建调用都将重新创建该窗口小部件。在initState或类构造函数中分配它并在以后引用它时,是否存在任何实际问题?

私有化示例

// this partial from inside the build() method currently
Container(
  padding: const EdgeInsets.only(bottom: 8.0),
    child: Text(
      'Oeschinen Lake Campground',
       style: TextStyle(
       fontWeight: FontWeight.bold,
    ),
  ),
 ),

// Now inside initState or class Constructor
myText = Text(
      'Oeschinen Lake Campground',
       style: TextStyle(
       fontWeight: FontWeight.bold,
    ),
 );

...
// Still inside build method (in fact this could also go inside the constructor ?)
Container( 
  padding: const EdgeInsets.only(bottom: 8.0),
  child: myText
)

这仅适用于不基于状态引用的代码。

以设计/美学的方式这样做是否还有其他弊端?尽管此示例很简单,但我一直认为,当应用变得更加复杂时,也许不必重新构建非基于状态的小部件可能会有一些性能/电池/顶空的好处(我也意识到我认为小部件会更进一步每次树都可能仍需要一个新对象)。但是,我是扑扑/飞镖的新手,所以要警惕一些我没有适当考虑的问题,或者从根本上误解了某些方面。

2 个答案:

答案 0 :(得分:5)

这是合法的优化。实际上,您甚至可以对状态依赖的小部件(与didUpdateWidget组合在一起)进行相同的操作。 不过,胜利微不足道。

小部件非常轻巧,Dart已针对许多微实例进行了优化。

这种方法有一个问题:您松开热装。


重用旧的窗口小部件实例仍然非常有用。仿佛小部件实例不变,Flutter放弃了子树的构建。

动画中经常使用它来每帧重建整个小部件树。一个典型的示例是AnimatedBuilder(但所有XXTransition都遵循相同的逻辑)

Animation animation;

AnimatedBuilder(
  animation: animation,
  child: Text('Foo'),
  builder: (context, child) {
    return Align(
      alignment: Alignment(.5, animation.value),
      child: child,
    );
  },
);

在这里,这会自愿重用child实例,这样就不会再次调用Text构建方法。


那么,我应该这样做吗?

是的,是的,不是的。优化您的应用程序总是很酷的。但是,除了使用变量之外,还有一种更好的方法:const构造函数。

要重用您的示例,您可以使用const构造函数将“始终相同”的小部件树提取到自定义小部件中:

class _Foo extends StatelessWidget {
  const _Foo({Key key}): super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      'Oeschinen Lake Campground',
      style: TextStyle(
        fontWeight: FontWeight.bold,
      ),
    );
  }
}

,然后在您的build方法中以这种方式使用它:

@override
Widget build(BuildContext context) {
  return Container(
    padding: const EdgeInsets.only(bottom: 8.0),
    child: const _Foo(),
  );
}

这样,您可以获得缓存小部件实例的好处。但是您不会丢失热装弹。

完全正确吗?

答案 1 :(得分:3)

TL; DR :不。


首先,让我们看看Googler和Flutter的工程师Matt Sullivan has to say about this topic

  

随着Flutter频繁地创建和销毁对象,   开发人员应该采取措施限制这种行为吗?这并不罕见   看到新的Flutter开发人员创建对他们知道的小部件的引用   不会随着时间的推移而改变,而是将它们置于状态,这样它们就不会   被摧毁并重建。

     

不要这样做。

为了理解为什么这不是一个好主意,您首先需要了解Widget的作用很少。 因为它们通常仅构成其他Widget或实例化RenderObject,所以它们不执行实际的渲染;所有繁重的工作都是由RenderObject完成的。 您可以将Widget视为RenderObject的超轻量级“蓝图”。
还要注意,RenderObject被Flutter框架本身大量缓存,因此,如果在连续的构建中创建一些类似的Widget,则基础RenderObject is automatically reused通过缓存用户界面,您可以有效地重新创建Flutter渲染管道提供的即用型功能。 因此,许多人认为增加代码的复杂性是不值得的。

“好吧,” 您可能会认为, “性能优势可能很小,但是确实存在,对吧?”

实际上,不。 这是因为基础Dart运行时使用两种类型的垃圾收集器:用于短期对象的 Young Space Scavenger 垃圾收集器和用于长期对象的 mark-sweep 垃圾收集器。 有关它们如何工作的更详细说明,请查看this article的恰当称呼:“不要害怕垃圾收集器”。
从根本上说,它表示 Young Space Scavenger mark-sweep 收集器要快得多,并且如果某个应用不遵循“弱世代假设”,则说明大多数对象会早逝,那么扫频的发生频率会更高。 简而言之,性能实际上可能会变差

总而言之,重新实现内置的Flutter功能同时使代码的可读性和应用速度变慢不是正确的方法。