[英]flutter: when are const widgets rebuilt?
I'm currently reading the example code of the provider package:我目前正在阅读提供程序包的示例代码:
// ignore_for_file: public_member_api_docs
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => Counter()),
],
child: Consumer<Counter>(
builder: (context, counter, _) {
return MaterialApp(
supportedLocales: const [Locale('en')],
localizationsDelegates: [
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
_ExampleLocalizationsDelegate(counter.count),
],
home: const MyHomePage(),
);
},
),
);
}
}
class ExampleLocalizations {
static ExampleLocalizations of(BuildContext context) =>
Localizations.of<ExampleLocalizations>(context, ExampleLocalizations);
const ExampleLocalizations(this._count);
final int _count;
String get title => 'Tapped $_count times';
}
class _ExampleLocalizationsDelegate
extends LocalizationsDelegate<ExampleLocalizations> {
const _ExampleLocalizationsDelegate(this.count);
final int count;
@override
bool isSupported(Locale locale) => locale.languageCode == 'en';
@override
Future<ExampleLocalizations> load(Locale locale) =>
SynchronousFuture(ExampleLocalizations(count));
@override
bool shouldReload(_ExampleLocalizationsDelegate old) => old.count != count;
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Title()),
body: const Center(child: CounterLabel()),
floatingActionButton: const IncrementCounterButton(),
);
}
}
class IncrementCounterButton extends StatelessWidget {
const IncrementCounterButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: Provider.of<Counter>(context).increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
);
}
}
class CounterLabel extends StatelessWidget {
const CounterLabel({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${counter.count}',
style: Theme.of(context).textTheme.display1,
),
],
);
}
}
class Title extends StatelessWidget {
const Title({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(ExampleLocalizations.of(context).title);
}
}
At first I was confused to see the following code.起初我看到下面的代码感到很困惑。 It is a MultiProvider, immediately followed by a Consumer, at the top of the Widget tree:
它是一个 MultiProvider,紧接着是一个 Consumer,位于 Widget 树的顶部:
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_)=>Counter()),
],
child: Consumer<Counter>(
builder: (context, counter, _){
return MaterialApp(
home: const MyHomePage()
);
},
),
);
I was wondering: Isn't this really bad for performance?我想知道:这对性能真的不好吗? Everytime the state of the consumer is updated, all the tree has to be rebuilt.
每次更新消费者的状态时,都必须重建所有树。 Then I realized the
const
qualifiers everywhere.然后我意识到无处不在的
const
限定符。 This seems like a very neat setup.这似乎是一个非常简洁的设置。 I decided to debug through it and see when and where widgets are rebuilt.
我决定通过它进行调试,看看何时何地重新构建小部件。
When the app is first started, flutter goes down the tree and builds the widgets one by one.当应用程序第一次启动时,flutter 会沿着树向下并一个一个地构建小部件。 This makes sense.
这是有道理的。
When the button is clicked and the Counter
is incremented, builder
is called on the Consumer at the very top of the tree.当单击按钮并且
Counter
增加时,在树的最顶部的消费者上调用builder
。 After that, build
is called on CounterLabel
and IncrementCounterButton
.在此之后,
build
被称为上CounterLabel
和IncrementCounterButton
。
CounterLabel
makes sense. CounterLabel
是有道理的。 This is not const
and will actually change its content.这不是
const
,实际上会改变它的内容。 But IncrementCounterButton
is marked as const
.但是
IncrementCounterButton
被标记为const
。 Why does it rebuild?为什么要重建?
It is not clear to me why some const
widgets are rebuilt while others aren't.我不清楚为什么一些
const
小部件会被重建,而另一些则不会。 What is the system behind this?这背后的系统是什么?
The most common reasons for a widget to rebuild are:小部件重建的最常见原因是:
Const instance of widgets are immune to the first reason, but they are still affected by the two others.小部件的 const 实例不受第一个原因的影响,但它们仍受其他两个原因的影响。
This means that a const instance of a StatelessWidget will rebuild only if one of the inherited widget it uses update.这意味着 StatelessWidget 的 const 实例只有在它使用的继承小部件之一更新时才会重建。
Provider is a convenient wrapper for InheritedWidget with a lot of nice things done for you. Provider 是 InheritedWidget 的一个方便的包装器,它为你做了很多好事。
Because IncrementCounterButton
accesses Provider (and InheritedWidget under the hood), it listens and rebuilds whenever the data changes.因为
IncrementCounterButton
访问 Provider(以及引擎盖下的 InheritedWidget),它会在数据发生变化时监听并重建。
To prevent buttons or other widgets that do not need to be rebuild on data change, set listen
to false
.为了防止不需要在数据更改时重建的按钮或其他小部件,请将
listen
设置为false
。
Provider.of(context, listen: false).increment
Provider.of(context, listen: false).increment
The caveat is that if the root widget rebuilds, widgets marked with listen: false
will still rebuild.需要注意的是,如果根小部件重建,标记为
listen: false
小部件仍将重建。 Understand how listen: false works when used with Provider<SomeType>.of(context, listen: false) 了解与 Provider<SomeType>.of(context, listen: false) 一起使用时 listen: false 的工作原理
Hope this helps!希望这可以帮助!
Building on @RayLi and @Remi's answers, another way to prevent a rebuild is to make this modification:基于@RayLi 和@Remi 的回答,另一种防止重建的方法是进行以下修改:
// onPressed: Provider.of<Counter>(context).increment, // This listens
onPressed: context.read<Counter>().increment, // this doesn't listen
context.read()
won't update, but in this case this is what you want. context.read()
不会更新,但在这种情况下,这就是您想要的。 onPressed
will be mapped to the same instance of .increment
throughout the FloatingActionButton's existence. onPressed
将被映射到相同的实例.increment
整个FloatingActionButton的存在。
context.read<Counter>()
has the same behavior as Provider.of<Counter>(context, listen: false)
. context.read<Counter>()
与Provider.of<Counter>(context, listen: false)
具有相同的行为。 See Is Provider.of(context, listen: false) equivalent to context.read()?请参阅Provider.of(context, listen: false) 是否等同于 context.read()?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.