在 StatefulWidget / State 中管理可变状态

Managing mutable state in StatefulWidget / State

我对如何使用 StatefulWidget 管理状态感到有点困惑。以下是我 运行 遇到的问题的简化示例:

我有一个 StatefulWidget 旨在显示事物列表。它是用从 API 检索到的列表构建的。该小部件还允许用户将新项目添加到列表中,只需点击 API 即可。 API 依次 returns 添加项目后的整个列表。

我 运行 遇到的挑战是小部件是用 api.List 对象构造的。 Dart 要求 StatefulWdigets 是不可变的(因此所有字段都是最终的),因此当用户向其添加项目时,该列表不能被 API 返回的新列表替换。这部分对我来说很有意义:可变状态应该存在于状态对象中,而不是小部件中。所以这表明状态对象应该跟踪列表。这会导致两个不同的问题:

  1. 状态对象是在StatefulWidgetcreateState方法中创建的。如果我们想将列表传递给状态对象的构造函数,则该列表也需要作为最终字段存储在 StatefulWidget 上。现在小部件及其状态对象都将引用该列表。当用户向列表中添加项目时,状态对象将其列表替换为从 API 返回的新副本,两者将不同步。

  2. 即使您这样做了,linter 也会抱怨在 createState 方法中包含逻辑:https://dart-lang.github.io/linter/lints/no_logic_in_create_state.html。似乎他们强烈建议状态对象应始终具有 0 参数构造函数,并且您永远不应将任何内容从 StatefulWidget 传递到 State 对象。他们建议始终通过 State 对象的 widget 访问此类数据。但这会让您回到 StatefulWidget 不可变的问题:如果它是 StatefulWidget 对象上的最终字段,您将无法替换列表。

有问题的设计 1

在这里,我们避免了 createState 方法中有逻辑的 linting 问题。但是你不能用 newList 替换 widget.list 因为它是最终的。

class ListWidget extends StatefulWidget {
  const ListWidget({Key? key, required this.list }) : super(key: key);
  
  final api.List list;
  
  @override
  State<StatefulWidget> createState() => _ListWidgetState();
}

class _ListWidgetState extends State<ListWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ...  // bunch of widgets
      ElevatedButton(
        ...
        onPressed: () async {
          final newList = await api.addNewListItem(...);
          // Can't do this because widget.list is final. Makes sense as it just seems wrong to store mutable state in the widget instead of the state.
          setState(() => widget.list = newList);
        },
      )
    );
  }
}

如果您不是替换 widget.list 对象,而是“内部”改变其所有字段以匹配 newList,则此设计可以工作。这看起来很尴尬而且容易出错。此外,您仍然会(在内部)发生变异 widget.list,这感觉很不对。看起来 flutter 的意图真的是可变状态真的不应该存在于 StatefulWidget.

有问题的设计 2

这有效,但是 linter 抱怨将列表传递给 createState 中的 _ListWidgetState 构造函数。此外,当用户向列表中添加项目时,小部件的 list 对象和状态的 list 对象变得不同步。

class ListWidget extends StatefulWidget {
  const ListWidget({Key? key, required this.list }) : super(key: key);

  final api.List list;

  @override
  State<StatefulWidget> createState() => _ListWidgetState(list); // linter complains
}

class _ListWidgetState extends State<ListWidget> {
  _ListWidgetState(this.list);

  api.List list;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ...  // bunch of widgets
      ElevatedButton(
        ...
        onPressed: () async {
          final newList = await api.addNewListItem(...);
          // This works, but now widget.list and list are out of sync.
          setState(() => list = newList);
        },
      )
    );
  }
}

Perils of Flutter state in StatefulWidget rather than State? 似乎在解决类似的问题。那里的建议是创建一个新的小部件实例。不过,我不确定如何从 onPressed 回调中用 newList 新建一个 ListWidget 完全替换 ListWidget

第二个片段更接近解决方案。本质上,ListWidget 中的 list_ListWidgetState 中的 list 没有必要“同步”。 其实最好看的是ListWidget里的list是初始数据,_ListWidgetState里的list是更新后的数据,如果有变化的话由用户提供。 您可以依赖第二个 list 中的数据,因为您在用户进行更改时维护它。

import 'package:flutter/material.dart';

class ListWidget extends StatefulWidget {
  const ListWidget({Key? key, required this.list }) : super(key: key);

  final api.List list;

  @override
  State<StatefulWidget> createState() => _ListWidgetState();
}

class _ListWidgetState extends State<ListWidget> {
  _ListWidgetState();

  late api.List list;

  @override
  void initState() {
    list = widget.list;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ... 
      ElevatedButton(
        ...
        onPressed: () async {
          list = await api.addNewListItem(...);
          setState(() {});
        },
      )
    );
  }
}