在 Dart 2 / Flutter 中使用 `const` 使用 "instantiating" 对象时有任何性能提升吗?
Any performance gain when using "instantiating" objects using `const` in Dart 2 / Flutter?
在 Dart 2 中,不再需要关键字 new
和 const
来实例化对象。例如,new ListView(...)
现在等同于 ListView(...)
.
但是,一些 class 构造函数被声明为 const
,包括大多数 Flutter 小部件,例如 Text
:
const Text(
String this.data, {
Key? key,
this.style,
// ...
this.textHeightBehavior,
})
然后,要实例化 Text
,我们可以键入 Text('hi!')
或 const Text('hi!')
。
我的问题:两者在性能或编译代码方面有什么不同吗?
注:
调用处const
的用法在某些地方比较突出,比如在AndroidStudio中,如果我们选择“Wrap with Padding”,EdgeInsets.all
构造函数调用使用 const
:
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('hi!'),
),
您应该尽可能尝试使用 const
,尤其是在您创建 Flutter 应用程序时。常量构造函数非常重要,因为它们用于“缓存”小部件,这样它们就不会被不必要地重建。
假设您的 Flutter 应用程序中有 10k 个项目的列表:
class Example extends StatelessWidget {
const Example();
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('My items:'),
ListView.builder(
shrinkWrap: true,
itemCount: 10000,
itemBuilder: (_, i) => SomeComplexWidget(
value: i,
),
),
],
);
}
}
有很多因素可以触发小部件重建,但我们假设您只是旋转了几次设备屏幕。使用此代码...
SizedBox(
width: 100,
height: 400,
child: Example(),
),
...您没有使用 const
构造函数,因此您在 每个 重建时重建 Example
(=您也不必要重建 Column
、ListView
,并重新渲染一些 SomeComplexWidget
)。 ListView.builder
是一个惰性初始化程序,因此它会为您节省一点,但您仍然不希望它总是被重建。
如果您为您的列表使用 Column()
或普通的 ListView()
构造函数,它会对性能产生更大的影响(更多的卡顿和更多的跳帧)。如果您改为这样做...
SizedBox(
width: 100,
height: 400,
child: const Example(),
),
...您将“缓存”Example
小部件(这意味着它将仅构建一次)。您可以猜到,这是您可以执行的非常简单但重要的性能优化。使用常量构造函数不仅是我的话,而且是文档官方推荐的:
- StatelessWidget 文档(请参阅性能注意事项)
- StatefulWidget 文档(请参阅性能注意事项)
请注意,如果您的小部件包含依赖于 InheritedWidget
的内容,则 const
关键字将不起作用(否则,小部件无法“监听”继承小部件的更改".
奖金
在 Flutter 中,您还可以“手动”缓存 widget。当不能使用const
时,可以在state内部使用late
(或late final
)来缓存widget。看这个例子:
class OtherExample extends StatelessWidget {
final List<Interests> interests;
const OtherExample({
required this.interests,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('My list'),
MyInterestsList(
data: interests,
),
]
);
}
}
您希望 MyInterestsList
被缓存,因为它永远不会改变,但 const
在这里不起作用(因为 interests
是 而不是 编译时)常量。当你不能使用 const
但你仍然想缓存一个小部件时,手动执行:
class OtherExample extends StatefulWidget {
final List<Interests> interests;
const OtherExample({
required this.interests,
});
@override
_OtherExampleState createState() => _OtherExampleState();
}
class _OtherExampleState extends State<OtherExample> {
late Widget myInterests = MyInterestsList(
data: widget.interests,
);
@override
void didUpdateWidget(OtherExample oldWidget) {
super.didUpdateWidget(oldWidget);
// This makes sure that the interests list is updated ONLY when
// it actually changes
if (widget.interests != oldWidget.interests) {
myInterests = MyInterestsList(
data: widget.interests,
);
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('My list'),
myInterests,
]
);
}
}
感谢 late
,您将小部件缓存在状态 class 中。由于 didUpdateWidget
覆盖,MyInterestsList
小部件将 只有 在列表更改时重建。如果列表永远不会改变,MyInterestsList
将永远不会重建。
在 Dart 2 中,不再需要关键字 new
和 const
来实例化对象。例如,new ListView(...)
现在等同于 ListView(...)
.
但是,一些 class 构造函数被声明为 const
,包括大多数 Flutter 小部件,例如 Text
:
const Text(
String this.data, {
Key? key,
this.style,
// ...
this.textHeightBehavior,
})
然后,要实例化 Text
,我们可以键入 Text('hi!')
或 const Text('hi!')
。
我的问题:两者在性能或编译代码方面有什么不同吗?
注:
调用处const
的用法在某些地方比较突出,比如在AndroidStudio中,如果我们选择“Wrap with Padding”,EdgeInsets.all
构造函数调用使用 const
:
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('hi!'),
),
您应该尽可能尝试使用 const
,尤其是在您创建 Flutter 应用程序时。常量构造函数非常重要,因为它们用于“缓存”小部件,这样它们就不会被不必要地重建。
假设您的 Flutter 应用程序中有 10k 个项目的列表:
class Example extends StatelessWidget {
const Example();
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('My items:'),
ListView.builder(
shrinkWrap: true,
itemCount: 10000,
itemBuilder: (_, i) => SomeComplexWidget(
value: i,
),
),
],
);
}
}
有很多因素可以触发小部件重建,但我们假设您只是旋转了几次设备屏幕。使用此代码...
SizedBox(
width: 100,
height: 400,
child: Example(),
),
...您没有使用 const
构造函数,因此您在 每个 重建时重建 Example
(=您也不必要重建 Column
、ListView
,并重新渲染一些 SomeComplexWidget
)。 ListView.builder
是一个惰性初始化程序,因此它会为您节省一点,但您仍然不希望它总是被重建。
如果您为您的列表使用 Column()
或普通的 ListView()
构造函数,它会对性能产生更大的影响(更多的卡顿和更多的跳帧)。如果您改为这样做...
SizedBox(
width: 100,
height: 400,
child: const Example(),
),
...您将“缓存”Example
小部件(这意味着它将仅构建一次)。您可以猜到,这是您可以执行的非常简单但重要的性能优化。使用常量构造函数不仅是我的话,而且是文档官方推荐的:
- StatelessWidget 文档(请参阅性能注意事项)
- StatefulWidget 文档(请参阅性能注意事项)
请注意,如果您的小部件包含依赖于 InheritedWidget
的内容,则 const
关键字将不起作用(否则,小部件无法“监听”继承小部件的更改".
奖金
在 Flutter 中,您还可以“手动”缓存 widget。当不能使用const
时,可以在state内部使用late
(或late final
)来缓存widget。看这个例子:
class OtherExample extends StatelessWidget {
final List<Interests> interests;
const OtherExample({
required this.interests,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('My list'),
MyInterestsList(
data: interests,
),
]
);
}
}
您希望 MyInterestsList
被缓存,因为它永远不会改变,但 const
在这里不起作用(因为 interests
是 而不是 编译时)常量。当你不能使用 const
但你仍然想缓存一个小部件时,手动执行:
class OtherExample extends StatefulWidget {
final List<Interests> interests;
const OtherExample({
required this.interests,
});
@override
_OtherExampleState createState() => _OtherExampleState();
}
class _OtherExampleState extends State<OtherExample> {
late Widget myInterests = MyInterestsList(
data: widget.interests,
);
@override
void didUpdateWidget(OtherExample oldWidget) {
super.didUpdateWidget(oldWidget);
// This makes sure that the interests list is updated ONLY when
// it actually changes
if (widget.interests != oldWidget.interests) {
myInterests = MyInterestsList(
data: widget.interests,
);
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('My list'),
myInterests,
]
);
}
}
感谢 late
,您将小部件缓存在状态 class 中。由于 didUpdateWidget
覆盖,MyInterestsList
小部件将 只有 在列表更改时重建。如果列表永远不会改变,MyInterestsList
将永远不会重建。