当你调用他们的setState时,Flutter widgets会对他们的状态变化做出反应。这听起来很简单,文档中给出的例子当然也很直接。
// 我显示一个名为myCounter的计数器,所以。
// 这是我设置状态的方法。
setState(() {
myCounter++。
});
其用意相当明显。效果相当明显。
我们现在都可以回家了。
别这么快... 让我们来读一读这些细节。
每当你改变一个State对象的内部状态时,请在传递给setState的函数中进行改变。
所以,我们应该将widget状态的变化包在该回调中。
等等,这总是可能的吗?
随着应用程序变得越来越复杂,它很难包含一个widget的 "状态"。你的widget将根据你可能无法控制的代码层启动。这些层会有自己的逻辑,管理自己的状态。
糟了 好像我们刚刚在widget本身之外提到了 "状态 "这个词。我们该怎么包装呢?
输入看起来很奇怪的空的setState调用。
class MyWidget ... {
void initState() {
// 我的数据源有变化
dataSource.listen(() {
setState(() {}); // Woot!
);
}
Widget build() {
// 使用我的数据源来建立一些东西
}
}
这不仅是奇怪的,而且是危险的。当我们对我们的代码进行审计时,我们发现有20%的setState的调用是空的。我们想重建widget的原因并不明显,也不是本地化的,所以我们只是在那里扔了一个setState。
class MyWidget ... {
void handleClick() {
// 在数据源中调用一些不知名的fn。
// 那有什么变化吗?不知道... 所以以防万一。
setState((){})。
}
Widget build() {
// 使用我的数据源来建立一些东西
}
}
我的意思是,它有效,但没有人真正明白为什么。提示下软件工程中最糟糕的争论: "嗯......如果我们把它拿出来,它就会坏掉。"
这不是好事。我们同时承受了两次打击。
-
性能打击
我们可能在做一堆不必要的重建。
-
可维护性打击
如果我们不理解代码,我们就无法维护它。
作为季度代码健康检查的一部分,我们分析了空setState的每一个用法,并记录了这些用例。
事实证明,除了一个挥之不去的用例外,你可以摆脱空状态,用适当的setState调用来代替它们:订阅一个不公布其价值的流。
我们再来看看第一个例子。我们在和一些不知名的模型对象进行交互,它发布了一个变化,但没有告诉我们是什么变化。所以,我们最后在空的回调中注释了大量的信息,说明我们希望widget如何受到变化的影响。
class MyWidget ... {
void initState() {
dataSource.listen(() => setState(() {
// 我的数据源发生了一些变化,并且
// 这个widget会用它来做x、y、z。
}));
}
Widget build() {
// 使用我的数据源来建立一些东西
}
}
这是我们代码库中唯一一个必须保留空状态回调的情况。我们在应用程序中发现的每一个其他用例,我们都能够将其转换为一个适当的setState调用。下面我介绍几个例子。
如果订阅公布其价值,那么就使用它。规范的做法是将变量设置为widgets状态的一部分,并在回调被触发时更新它。
class MyWidget ... {
MyObject _object。
void initState() {
dataSource.listen((object)=> setState(() {
// 将对象设置为新的状态
_object = object;
}));
}
Widget build() {
// 使用_object来构建一些东西
}
}
如果你面对的不是流,而你又不得不设定一个空的状态,那么请你对着镜子仔细地看一看自己,问一问:
我的构建函数中发生了什么?
检查构建函数可以让您很好地了解widget真正需要的是什么,以及它可能会受到什么样的变化的影响。考虑一下我们在代码中发现的这个 "宝石"。
class MyWidget ... {
Widget build() {
// 对当前时间做一些事情
}
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == resumed) {
setState((){})。
}
}
}
乍一看,这看起来完全没问题。当应用恢复时,我们希望这个widget能够刷新。然而,原因并不明显。
当我们查看构建函数时,我们看到这个widget实际上对当前时间做了一些事情。这让人很困惑。这就是这个widget的状态所依赖的吗?每当currentTime改变时,我们是否应该重建这个widget?是的!
这迫使我们提出以下问题。
如果除了预期的状态变化(如屏幕旋转)之外,因为任何原因调用构建函数,会发生什么?
我们发现,上面的问题是测试widget行为的一个很好的试金石。上面例子中的widget在重建时表现得出乎意料,因为它取决于构建时间而不是恢复时间,而这是它的初衷。 这个widget的正确写法是。
class MyWidget ... {
DateTime _lastResumeTimestamp;
Widget build() {
// 用_lastResumeTimestamp做一些事情。
}
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == resumed) {
setState(() {
_lastResumeTimestamp = currentTime;
});
}
}
}
不再意外重建。不再有混乱的代码。
不再是空状态回调。
编码快乐!
通过www.DeepL.com/Translator(免费版)翻译