如何处理不需要的小部件构建?


142

由于各种原因,有时会build再次调用我的小部件的方法。

我知道这是因为父母更新了。但这会导致不良后果。导致问题的典型情况是使用FutureBuilder这种方式:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: httpCall(),
    builder: (context, snapshot) {
      // create some layout here
    },
  );
}

在此示例中,如果再次调用build方法,它将触发另一个http请求。这是不希望的。

考虑到这一点,如何处理不必要的构建?有什么办法可以防止构建调用吗?



3
提供者文档中,您在此处链接:“查看此stackoverflow答案,该答案进一步详细说明了为什么不希望使用.value构造函数来创建值。” 但是,您在此处或您的答案中没有提及值构造函数。您是要链接到其他地方吗?
苏拉奇

Answers:


223

构建方法的设计是这样一种方式,它应该是纯/无副作用。这是因为许多外部因素都可以触发新的小部件构建,例如:

  • 路线弹出/推入
  • 屏幕尺寸调整,通常是由于键盘外观或方向改变
  • 父小部件重新创建了它的子级
  • 小部件所依赖的InheritedWidget(Class.of(context)模式更改)

这意味着该build方法应该不会触发HTTP调用或修改任何状态


这与问题有什么关系?

您面临的问题是您的构建方法有副作用/不是纯净的,这使多余的构建调用变得很麻烦。

不应阻止构建调用,而应使构建方法纯净,以便可以在没有影响的情况下随时调用它。

在您的例子中,你会改变你的工具到一个StatefulWidget然后解压缩HTTP调用initState你的State

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<int> future;

  @override
  void initState() {
    future = Future.value(42);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, snapshot) {
        // create some layout here
      },
    );
  }
}

我已经知道了 我来这里是因为我真的很想优化重建

也有可能使小部件能够重建而无需强迫其子代进行构建。

当小部件的实例保持不变时;Flutter故意不会重建孩子。这意味着您可以缓存小部件树的一部分,以防止不必要的重建。

最简单的方法是使用dart const构造函数:

@override
Widget build(BuildContext context) {
  return const DecoratedBox(
    decoration: BoxDecoration(),
    child: Text("Hello World"),
  );
}

多亏了该const关键字,DecoratedBox即使调用了数百次构建,的实例也将保持不变。

但是您可以手动实现相同的结果:

@override
Widget build(BuildContext context) {
  final subtree = MyWidget(
    child: Text("Hello World")
  );

  return StreamBuilder<String>(
    stream: stream,
    initialData: "Foo",
    builder: (context, snapshot) {
      return Column(
        children: <Widget>[
          Text(snapshot.data),
          subtree,
        ],
      );
    },
  );
}

在此示例中,当StreamBuilder收到新值通知时,subtree即使StreamBuilder / Column这样做也不会重建。发生这种情况的原因是,由于有了闭包,的实例MyWidget没有改变。

动画中经常使用这种模式。通常的用途是AnimatedBuilder和所有过渡,例如AlignTransition

您也可以将其存储subtree到班级的某个字段中,尽管不建议这样做,因为它会破坏热重装功能。


2
您能否解释为什么subtree在类字段中存储会中断热重装?
mFeinstein '19

4
我遇到的一个问题StreamBuilder是,当键盘出现时,屏幕会发生变化,因此必须重新构建路由。如此StreamBuilder重建并StreamBuilder创建一个新的并订阅stream。当a StreamBuilder订阅时streamsnapshot.connectionState变为ConnectionState.waiting,使我的代码返回CircularProgressIndicator,然后snapshot.connectionState在有数据时更改,并且我的代码将返回不同的小部件,这使屏幕上闪烁着不同的内容。
mFeinstein '19

1
我决定做一个StatefulWidget,订阅streaminitState(),并设置currentWidgetsetState()作为stream发送新数据,传递currentWidgetbuild()方法。有更好的解决方案吗?
mFeinstein '19

1
我有点困惑。您正在回答自己的问题,但是从内容上看,它看起来并不像它。
sgon00

8
嗯,说构建不应该调用HTTP方法会完全击败a的非常实际的示例FutureBuilder
TheGeekZn '19

6

您可以使用以下方式防止不必要的构建调用

1)为UI的一小部分创建子Statefull类

2)使用提供程序库,因此使用它可以停止不需要的生成方法调用

在以下情况下,生成方法调用


0

扑也ValueListenableBuilder<T> class 。它仅允许您重建一些您所需的小部件,而跳过昂贵的小部件。

您可以在此处查看ValueListenableBuilder flutter文档
或仅以下示例代码的文档

  return Scaffold(
  appBar: AppBar(
    title: Text(widget.title)
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('You have pushed the button this many times:'),
        ValueListenableBuilder(
          builder: (BuildContext context, int value, Widget child) {
            // This builder will only get called when the _counter
            // is updated.
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text('$value'),
                child,
              ],
            );
          },
          valueListenable: _counter,
          // The child parameter is most helpful if the child is
          // expensive to build and does not depend on the value from
          // the notifier.
          child: goodJob,
        )
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.plus_one),
    onPressed: () => _counter.value += 1,
  ),
);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.