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

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

我知道这是因为父母更新了。但这会造成不良影响。 导致问题的典型情况是这样使用FutureBuilder:

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

在这个例子中,如果构建方法被再次调用,它将触发另一个HTTP请求。这是不受欢迎的。

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

86175 次浏览

构建方法被设计成纯/无副作用。这是因为许多外部因素可以触发一个新的小部件构建,例如:

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

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


这和问题有什么关系?

你面临的问题是你的构建方法有副作用/不纯粹,使得无关的构建调用很麻烦。

您应该使您的构建方法纯,而不是阻止构建调用,以便它可以在任何时候被调用而不受影响。

在你的例子中,你会把你的小部件转换成一个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
},
);
}
}

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

也可以让一个小部件能够在不强迫其子部件也进行构建的情况下进行重建。

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

最简单的方法是使用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被通知有新值时,即使StreamBuilder/Column重新构建,subtree也不会重新构建。 它的发生是因为,由于闭包,MyWidget的实例没有改变

这种模式在动画中被大量使用。典型的使用是AnimatedBuilder和所有的转换,如AlignTransition

你也可以将subtree存储到你的类的一个字段中,尽管不太推荐,因为它破坏了热重载特性。

使用这些方法,可以防止不必要的构建调用

  1. 为UI的个别小部分创建子状态类

  2. 使用提供者库,所以使用它你可以停止不需要的构建方法调用

在以下这些情况下构建方法调用

Flutter也有ValueListenableBuilder<T> class 。它允许您只重新构建您的目的所必需的一些小部件,而跳过昂贵的小部件。

你可以在这里看到文档ValueListenableBuilder颤振文档
或者只是下面的示例代码:

  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,
),
);
避免不必要的re__abc3的最简单的方法之一,通常是通过调用setState()来只更新特定的小部件而不刷新整个页面而引起的,是删除这部分代码并将其包装为另一个Stateful类中的独立Widget
例如,在以下代码中,通过按下FAB按钮反复调用父页的Build方法:

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


但是如果你将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)
));
}
}


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


我只是想分享我的经验,不需要的小部件构建主要是由于上下文,但我发现了一种非常有效的方法

  • 路线流行/推动

因此,您需要使用Navigator.pushReplacement(),以便上一页的上下文与即将到来的页面没有关系

  1. 使用Navigator.pushReplacement()从第一页导航到第二页
  2. 在第二页中,我们需要再次使用Navigator.pushReplacement() 在appBar中我们添加了-
    leading: IconButton(
    icon: Icon(Icons.arrow_back),
    onPressed: () {
    Navigator.pushReplacement(
    context,
    RightToLeft(page: MyHomePage()),
    );
    },
    )
    

通过这种方式,我们可以优化应用程序

你可以这样做:

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


}