短时 (ephemeral) 和应用 (app) 状态的区别

本文将介绍应用 (app)状态,短时 (ephemeral) 状态, 以及在一个 Flutter 应用中你可以如何应用这两种状态

This doc introduces app state, ephemeral state, and how you might manage each in a Flutter app.

广义上来讲,一个应用的状态就是当这个应用运行时存在于内存中的所有内容。这包括了应用中用到的资源,所有 Flutter 框架中有关用户界面、动画状态、纹理、字体以及其他等等的变量。这个对于状态广义的定义是有效的,但是它对于构建一个应用来说并不是很有用。

In the broadest possible sense, the state of an app is everything that exists in memory when the app is running. This includes the app’s assets, all the variables that the Flutter framework keeps about the UI, animation state, textures, fonts, and so on. While this broadest possible definition of state is valid, it’s not very useful for architecting an app.

首先,你不需要管理一些状态(例如纹理),框架本身会替你管理。所以对于状态的更有用的定义是“当任何时候你需要重建你的用户界面时你所需要的数据”。其次,你需要自己管理的状态可以分为两种概念类型:短时 (ephemeral) 状态和应用 (app)状态。

First, you don’t even manage some state (like textures). The framework handles those for you. So a more useful definition of state is “whatever data you need in order to rebuild your UI at any moment in time”. Second, the state that you do manage yourself can be separated into two conceptual types: ephemeral state and app state.

短时状态

Ephemeral state

短时状态 (有时也称 用户界面(UI)状态 或者 局部状态) 是你可以完全包含在一个独立 widget 中的状态。

Ephemeral state (sometimes called UI state or local state) is the state you can neatly contain in a single widget.

这是一个有点儿模糊的定义,这里有几个例子。

This is, intentionally, a vague definition, so here are a few examples.

  • 一个 PageView 组件中的当前页面

    current page in a PageView

  • 一个复杂动画中当前进度

    current progress of a complex animation

  • 一个 BottomNavigationBar 中当前被选中的 tab

    current selected tab in a BottomNavigationBar

widget 树中其他部分不需要访问这种状态。不需要去序列化这种状态,这种状态也不会以复杂的方式改变。

Other parts of the widget tree seldom need to access this kind of state. There is no need to serialize it, and it doesn’t change in complex ways.

换句话说,不需要使用状态管理架构(例如 ScopedModel, Redux)去管理这种状态。你需要用的只是一个 StatefulWidget

In other words, there is no need to use state management techniques (ScopedModel, Redux, etc.) on this kind of state. All you need is a StatefulWidget.

在下方你可以看到一个底部导航栏中当前被选中的项目是如何被被保存在 _MyHomepageState 类的 _index 变量中。在这个例子中, _index 是一个短时状态。

Below, you see how the currently selected item in a bottom navigation bar is held in the _index field of the _MyHomepageState class. In this example, _index is ephemeral state.

class MyHomepage extends StatefulWidget {
  @override
  _MyHomepageState createState() => _MyHomepageState();
}

class _MyHomepageState extends State<MyHomepage> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      currentIndex: _index,
      onTap: (newIndex) {
        setState(() {
          _index = newIndex;
        });
      },
      // ... items ...
    );
  }
}

在这里,使用 setState() 和一个在有状态 Widget 的 State 类中的变量是很自然的。你的 app 中的其他部分不需要访问 _index。这个变量只会在 MyHomepage widget 中改变。而且,如果用户关闭并重启这个 app,你不会介意 _index 重置回0.

Here, using setState() and a field inside the StatefulWidget’s State class is completely natural. No other part of your app needs to access _index. The variable only changes inside the MyHomepage widget. And, if the user closes and restarts the app, you don’t mind that _index resets to zero.

应用状态

App state

如果你想在你的应用中的多个部分之间共享一个非短时的状态,并且在用户会话期间保留这个状态,我们称之为应用状态(有时也称共享状态)。

State that is not ephemeral, that you want to share across many parts of your app, and that you want to keep between user sessions, is what we call application state (sometimes also called shared state).

应用状态的一些例子:

Examples of application state:

  • 用户选项

    User preferences

  • 登录信息

    Login info

  • 一个社交应用中的通知

    Notifications in a social networking app

  • 一个电商应用中的购物车

    The shopping cart in an e-commerce app

  • 一个新闻应用中的文章已读/未读状态

    Read/unread state of articles in a news app

为了管理应用状态,你需要研究你的选项。你的选择取决于你的应用的复杂度和限制,你的团队之前的经验以及其他方面。请继续阅读。

For managing app state, you’ll want to research your options. Your choice depends on the complexity and nature of your app, your team’s previous experience, and many other aspects. Read on.

没有明确的规则

There is no clear-cut rule

需要说明的是,你可以使用 StatesetState() 管理你的应用中的所有状态。实际上Flutter团队在很多简单的示例程序(包括你每次使用 flutter create 命令创建的初始应用)中正是这么做的。

To be clear, you can use State and setState() to manage all of the state in your app. In fact, the Flutter team does this in many simple app samples (including the starter app that you get with every flutter create).

也可以用另外一种方式。比如,在一个特定的应用中,你可以指定底部导航栏中被选中的项目不是一个短时状态。你可能需要在底部导航栏类的外部来改变这个值,并在对话期间保留它。在种情况下 _index 就是一个应用状态。

It goes the other way, too. For example, you might decide that—in the context of your particular app—the selected tab in a bottom navigation bar is not ephemeral state. You might need to change it from outside the class, keep it between sessions, and so on. In that case, the _index variable is app state.

没有一个明确、普遍的规则来区分一个变量属于短时状态还是应用状态,有时你不得不在此之间重构。比如,刚开始你认为一些状态是短时状态,但随着应用不断增加功能,有些状态需要被改变为应用状态。

There is no clear-cut, universal rule to distinguish whether a particular variable is ephemeral or app state. Sometimes, you’ll have to refactor one into another. For example, you’ll start with some clearly ephemeral state, but as your application grows in features, it will need to be moved to app state.

因此,请有保留地遵循以下这张流程图:

For that reason, take the following diagram with a large grain of salt:

A flow chart. Start with 'Data'. 'Who needs it?'. Three options: 'Most widgets', 'Some widgets' and 'Single widget'. The first two options both lead to 'App state'. The 'Single widget' option leads to 'Ephemeral state'.

当我们就 React 的 setState 和 Redux 的 Store 哪个好这个问题问 Redux 的作者 Dan Abramov 时, 他如此回答:

When asked about React’s setState versus Redux’s store, the author of Redux, Dan Abramov, replied:

“经验原则是: 选择能够减少麻烦的方式

“The rule of thumb is: Do whatever is less awkward.”

总之,在任何 Flutter 应用中都存在两种概念类型的状态,短时状态经常被用于一个单独 widget 的本地状态,通常使用 StatesetState() 来实现。其他的是你的应用应用状态,在任何一个 Flutter 应用中这两种状态都有自己的位置。如何划分这两种状态取决于你的偏好以及应用的复杂度。

In summary, there are two conceptual types of state in any Flutter app. Ephemeral state can be implemented using State and setState(), and is often local to a single widget. The rest is your app state. Both types have their place in any Flutter app, and the split between the two depends on your own preference and the complexity of the app.