[Flutter翻译]避免空状态回调

550 阅读4分钟

原文地址:medium.com/@mehmetf_71…

原文作者:medium.com/@mehmetf_71…

当你调用他们的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(免费版)翻译