简体   繁体   English

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

[英]How to deal with unwanted widget build?

For various reasons, sometimes the build method of my widgets is called again.由于各种原因,有时会再次调用我的小部件的build方法。

I know that it happens because a parent updated.我知道这是因为父母更新了。 But this causes undesired effects.但这会导致不良影响。 A typical situation where it causes problems is when using FutureBuilder this way:导致问题的典型情况是以这种方式使用FutureBuilder时:

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

In this example, if the build method were to be called again, it would trigger another HTTP request.在这个例子中,如果再次调用build方法,它会触发另一个 HTTP 请求。 Which is undesired.这是不希望的。

Considering this, how to deal with the unwanted build?考虑到这一点,如何处理不需要的构建? Is there any way to prevent a build call?有什么方法可以防止构建调用?

The build method is designed in such a way that it should be pure/without side effects .构建方法的设计方式应该是纯粹的/没有副作用 This is because many external factors can trigger a new widget build, such as:这是因为许多外部因素可以触发新的小部件构建,例如:

  • Route pop/push路由弹出/推送
  • Screen resize, usually due to keyboard appearance or orientation change屏幕调整大小,通常是由于键盘外观或方向改变
  • Parent widget recreated its child父小部件重新创建了它的子部件
  • An InheritedWidget the widget depends on ( Class.of(context) pattern) change小部件依赖的 InheritedWidget ( Class.of(context)模式) 更改

This means that the build method should not trigger an http call or modify any state .这意味着build方法不应触发HTTP调用或修改任何状态


How is this related to the question?这与问题有什么关系?

The problem you are facing is that your build method has side-effects/is not pure, making extraneous build call troublesome.您面临的问题是您的构建方法有副作用/不纯,使无关的构建调用变得麻烦。

Instead of preventing build call, you should make your build method pure, so that it can be called anytime without impact.与其阻止构建调用,您应该使构建方法纯,以便可以随时调用而不会产生影响。

In the case of your example, you'd transform your widget into a StatefulWidget then extract that HTTP call to the initState of your State :在您的示例中,您将小部件转换为StatefulWidget然后将该 HTTP 调用提取到StateinitState

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
      },
    );
  }
}

I know this already.我已经知道了。 I came here because I really want to optimize rebuilds我来这里是因为我真的很想优化重建

It is also possible to make a widget capable of rebuilding without forcing its children to build too.也可以使小部件能够重建,而无需强迫其子部件也进行构建。

When the instance of a widget stays the same;当小部件的实例保持不变时; Flutter purposefully won't rebuild children. Flutter 故意不会重建孩子。 It implies that you can cache parts of your widget tree to prevent unnecessary rebuilds.这意味着您可以缓存部分小部件树以防止不必要的重建。

The easiest way is to use dart const constructors:最简单的方法是使用 dart const构造函数:

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

Thanks to that const keyword, the instance of DecoratedBox will stay the same even if build were called hundreds of times.感谢这个const关键字,即使 build 被调用数百次, DecoratedBox的实例也将保持不变。

But you can achieve the same result manually:但是您可以手动获得相同的结果:

@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,
        ],
      );
    },
  );
}

In this example when StreamBuilder is notified of new values, subtree won't rebuild even if the StreamBuilder/Column do.在此示例中,当 StreamBuilder 收到新值通知时,即使 StreamBuilder/Column 重建subtree也不会重建。 It happens because, thanks to the closure, the instance of MyWidget didn't change.这是因为,由于关闭, MyWidget的实例没有改变。

This pattern is used a lot in animations.这种模式在动画中被大量使用。 Typical uses are AnimatedBuilder and all transitions such as AlignTransition .典型用途是AnimatedBuilder和所有过渡,例如AlignTransition

You could also store subtree into a field of your class, although less recommended as it breaks the hot-reload feature.您也可以将subtree存储到您的类的一个字段中,尽管不太推荐,因为它破坏了热重载功能。

You can prevent unwanted build calling, using these way您可以使用这些方式防止不需要的构建调用

1) Create child Statefull class for individual small part of UI 1) 为 UI 的各个小部分创建子 Statefull 类

2) Use Provider library, so using it you can stop unwanted build method calling 2) 使用Provider库,因此使用它可以停止不需要的构建方法调用

In these below situation build method call在以下这些情况下构建方法调用

Flutter also has ValueListenableBuilder<T> class . Flutter 也有ValueListenableBuilder<T> class It allows you to rebuild only some of the widgets necessary for your purpose and skip the expensive widgets.它允许您只重建一些您的目的所必需的小部件,而跳过昂贵的小部件。

you can see the documents here ValueListenableBuilder flutter docs你可以在这里查看文档ValueListenableBuilder flutter docs
or just the sample code below:或者只是下面的示例代码:

  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,
  ),
);

One of the easiest ways to avoid unwanted re Build s that are caused usually by calling setState() in order to update only a specific Widget and not refreshing the whole page, is to cut that part of your code and wrap it as an independent Widget in another Stateful class.避免不需要的 re Build的最简单方法之一,通常是通过调用setState()来仅更新特定的 Widget 而不是刷新整个页面而引起的,是剪切该部分代码并将其包装为独立的Widget在另一个Stateful类中。
For example in following code, Build method of parent page is called over and over by pressing the FAB button:例如在下面的代码中,父页面的Build方法通过按下 FAB 按钮反复调用:

import 'package:flutter/material.dart';

void main() {
  runApp(TestApp());
}

class TestApp extends StatefulWidget {

  @override
  _TestAppState createState() => _TestAppState();
}

class _TestAppState extends State<TestApp> {

  int c = 0;

  @override
  Widget build(BuildContext context) {

    print('build is called');

    return MaterialApp(home: Scaffold(
      appBar: AppBar(
        title: Text('my test app'),
      ),
      body: Center(child:Text('this is a test page')),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          setState(() {
            c++;
          });
        },
        tooltip: 'Increment',
        child: Icon(Icons.wb_incandescent_outlined, color: (c % 2) == 0 ? Colors.white : Colors.black)
      )
    ));
  }
}

But if you separate the FloatingActionButton widget in another class with its own life cycle, setState() method does not cause the parent class Build method to re-run:但是如果将 FloatingActionButton 小部件分离到另一个具有自己生命周期的类中, setState()方法不会导致父类Build方法重新运行:

import 'package:flutter/material.dart';
import 'package:flutter_app_mohsen/widgets/my_widget.dart';

void main() {
  runApp(TestApp());
}

class TestApp extends StatefulWidget {

  @override
  _TestAppState createState() => _TestAppState();
}

class _TestAppState extends State<TestApp> {

  int c = 0;

  @override
  Widget build(BuildContext context) {

    print('build is called');

    return MaterialApp(home: Scaffold(
      appBar: AppBar(
        title: Text('my test app'),
      ),
      body: Center(child:Text('this is a test page')),
      floatingActionButton: MyWidget(number: c)
    ));
  }
}

and the MyWidget class:和 MyWidget 类:

import 'package:flutter/material.dart';

class MyWidget extends StatefulWidget {

  int number;
  MyWidget({this.number});

  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
        onPressed: (){
          setState(() {
            widget.number++;
          });
        },
        tooltip: 'Increment',
        child: Icon(Icons.wb_incandescent_outlined, color: (widget.number % 2) == 0 ? Colors.white : Colors.black)
    );
  }
}

You can do something like this:你可以这样做:

  class Example extends StatefulWidget {
      @override
      _ExampleState createState() => _ExampleState();
    }
    
    class _ExampleState extends State<Example> {
      Future<int> future;
    
      @override
      void initState() {
        future = httpCall();
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
          future: future,
          builder: (context, snapshot) {
            // create some layout here
          },
        );
      }
    
    
     void refresh(){
      setState((){
       future = httpCall();
       });
    }

  }

I just want to share my experience of unwanted widget build mainly due to context but I found a way that is very effective for我只想分享我主要由于上下文而构建不需要的小部件的经验,但我找到了一种非常有效的方法

  • Route pop/push路由弹出/推送

So you need to use Navigator.pushReplacement() so that the context of the previous page has no relation with the upcoming page所以你需要使用 Navigator.pushReplacement() 使上一页的上下文与下一页没有关系

  1. Use Navigator.pushReplacement() for navigating from the first page to Second使用 Navigator.pushReplacement() 从第一页导航到第二页
  2. In second page again we need to use Navigator.pushReplacement() In appBar we add -再次在第二页我们需要使用 Navigator.pushReplacement() 在 appBar 我们添加 -
     leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () { Navigator.pushReplacement( context, RightToLeft(page: MyHomePage()), ); }, )

In this way we can optimize our app这样我们就可以优化我们的应用程序

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM