如何处理不需要的小部件构建?
How to deal with unwanted widget build?
由于各种原因,有时会再次调用我的小部件的 build
方法。
我知道这是因为家长更新了。但这会导致不良影响。
导致问题的典型情况是这样使用 FutureBuilder
:
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: httpCall(),
builder: (context, snapshot) {
// create some layout here
},
);
}
在此示例中,如果再次调用 build 方法,它将触发另一个 HTTP 请求。这是不受欢迎的。
考虑到这一点,如何处理不需要的构建?有什么方法可以阻止构建调用吗?
build方法的设计方式应该是pure/without副作用。这是因为许多外部因素可以触发新的小部件构建,例如:
- 路线pop/push
- 屏幕大小调整,通常是由于键盘外观或方向改变
- parent 小部件重新创建了它的 child
- 一个 InheritedWidget 小部件依赖于(
Class.of(context)
模式)更改
这意味着build
方法应该不触发http调用或修改任何状态.
这与问题有什么关系?
您面临的问题是您的构建方法有一面 effects/is 不纯,使得无关的构建调用很麻烦。
与其阻止构建调用,不如让构建方法更纯粹,这样它就可以随时调用而不会产生影响。
在您的示例中,您会将小部件转换为 StatefulWidget
,然后将该 HTTP 调用提取到 State
:
的 initState
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
也可以使小部件能够重建,而无需强制其 children 也进行构建。
当小部件的实例保持不变时; Flutter 故意不重建 children。这意味着您可以缓存部分小部件树以防止不必要的重建。
最简单的方法是使用 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
存储到 class 的字段中,但不太推荐,因为它会破坏 hot-reload 功能。
您可以使用这些方式防止不需要的构建调用
为 UI
的个别小部分创建子 Statefull class
使用 Provider 库,因此使用它可以停止不需要的构建方法调用
在以下这些情况下构建方法调用
- 调用后initState
- 调用后didUpdateWidget
- 当 setState() 被调用时。
- 当键盘打开时
- 当屏幕方向改变时
- 如果构建了父控件,那么也会重建子控件
Flutter 也有ValueListenableBuilder<T> class
。它允许您仅重建您的目的所需的一些小部件,并跳过昂贵的小部件。
你可以在这里看到文件ValueListenableBuilder flutter docs
或者只是下面的示例代码:
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,
),
);
避免不必要的重新Builds 的最简单方法之一通常是调用 setState()
以仅更新特定的 Widget 而不是刷新整个 Widget页面,就是把你的那部分代码剪下来,然后把它作为一个独立的 Widget
包装在另一个 Stateful
class.
中
例如在下面的代码中,父页面的 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)
)
));
}
}
但是如果您将 FloatingActionButton 小部件分离到另一个 class 中并具有自己的生命周期,setState()
方法不会导致父 class 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 class:
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)
);
}
}
我只是想分享我主要由于上下文而构建不需要的小部件的经验,但我找到了一种非常有效的方法
- 路线pop/push
所以你需要使用 Navigator.pushReplacement() 使得上一页的上下文与下一页没有关系
- 使用 Navigator.pushReplacement() 从第一页导航到第二页
- 在第二页我们需要再次使用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();
});
}
}
由于各种原因,有时会再次调用我的小部件的 build
方法。
我知道这是因为家长更新了。但这会导致不良影响。
导致问题的典型情况是这样使用 FutureBuilder
:
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: httpCall(),
builder: (context, snapshot) {
// create some layout here
},
);
}
在此示例中,如果再次调用 build 方法,它将触发另一个 HTTP 请求。这是不受欢迎的。
考虑到这一点,如何处理不需要的构建?有什么方法可以阻止构建调用吗?
build方法的设计方式应该是pure/without副作用。这是因为许多外部因素可以触发新的小部件构建,例如:
- 路线pop/push
- 屏幕大小调整,通常是由于键盘外观或方向改变
- parent 小部件重新创建了它的 child
- 一个 InheritedWidget 小部件依赖于(
Class.of(context)
模式)更改
这意味着build
方法应该不触发http调用或修改任何状态.
这与问题有什么关系?
您面临的问题是您的构建方法有一面 effects/is 不纯,使得无关的构建调用很麻烦。
与其阻止构建调用,不如让构建方法更纯粹,这样它就可以随时调用而不会产生影响。
在您的示例中,您会将小部件转换为 StatefulWidget
,然后将该 HTTP 调用提取到 State
:
initState
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
也可以使小部件能够重建,而无需强制其 children 也进行构建。
当小部件的实例保持不变时; Flutter 故意不重建 children。这意味着您可以缓存部分小部件树以防止不必要的重建。
最简单的方法是使用 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
存储到 class 的字段中,但不太推荐,因为它会破坏 hot-reload 功能。
您可以使用这些方式防止不需要的构建调用
为 UI
的个别小部分创建子 Statefull class使用 Provider 库,因此使用它可以停止不需要的构建方法调用
在以下这些情况下构建方法调用
- 调用后initState
- 调用后didUpdateWidget
- 当 setState() 被调用时。
- 当键盘打开时
- 当屏幕方向改变时
- 如果构建了父控件,那么也会重建子控件
Flutter 也有ValueListenableBuilder<T> class
。它允许您仅重建您的目的所需的一些小部件,并跳过昂贵的小部件。
你可以在这里看到文件ValueListenableBuilder flutter docs
或者只是下面的示例代码:
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,
),
);
避免不必要的重新Builds 的最简单方法之一通常是调用 setState()
以仅更新特定的 Widget 而不是刷新整个 Widget页面,就是把你的那部分代码剪下来,然后把它作为一个独立的 Widget
包装在另一个 Stateful
class.
中
例如在下面的代码中,父页面的 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)
)
));
}
}
但是如果您将 FloatingActionButton 小部件分离到另一个 class 中并具有自己的生命周期,setState()
方法不会导致父 class 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 class:
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)
);
}
}
我只是想分享我主要由于上下文而构建不需要的小部件的经验,但我找到了一种非常有效的方法
- 路线pop/push
所以你需要使用 Navigator.pushReplacement() 使得上一页的上下文与下一页没有关系
- 使用 Navigator.pushReplacement() 从第一页导航到第二页
- 在第二页我们需要再次使用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();
});
}
}