在 StatefulWidget / State 中管理可变状态
Managing mutable state in StatefulWidget / State
我对如何使用 StatefulWidget 管理状态感到有点困惑。以下是我 运行 遇到的问题的简化示例:
我有一个 StatefulWidget
旨在显示事物列表。它是用从 API 检索到的列表构建的。该小部件还允许用户将新项目添加到列表中,只需点击 API 即可。 API 依次 returns 添加项目后的整个列表。
我 运行 遇到的挑战是小部件是用 api.List
对象构造的。 Dart 要求 StatefulWdiget
s 是不可变的(因此所有字段都是最终的),因此当用户向其添加项目时,该列表不能被 API 返回的新列表替换。这部分对我来说很有意义:可变状态应该存在于状态对象中,而不是小部件中。所以这表明状态对象应该跟踪列表。这会导致两个不同的问题:
状态对象是在StatefulWidget
的createState
方法中创建的。如果我们想将列表传递给状态对象的构造函数,则该列表也需要作为最终字段存储在 StatefulWidget
上。现在小部件及其状态对象都将引用该列表。当用户向列表中添加项目时,状态对象将其列表替换为从 API 返回的新副本,两者将不同步。
即使您这样做了,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(() {});
},
)
);
}
}
我对如何使用 StatefulWidget 管理状态感到有点困惑。以下是我 运行 遇到的问题的简化示例:
我有一个 StatefulWidget
旨在显示事物列表。它是用从 API 检索到的列表构建的。该小部件还允许用户将新项目添加到列表中,只需点击 API 即可。 API 依次 returns 添加项目后的整个列表。
我 运行 遇到的挑战是小部件是用 api.List
对象构造的。 Dart 要求 StatefulWdiget
s 是不可变的(因此所有字段都是最终的),因此当用户向其添加项目时,该列表不能被 API 返回的新列表替换。这部分对我来说很有意义:可变状态应该存在于状态对象中,而不是小部件中。所以这表明状态对象应该跟踪列表。这会导致两个不同的问题:
状态对象是在
StatefulWidget
的createState
方法中创建的。如果我们想将列表传递给状态对象的构造函数,则该列表也需要作为最终字段存储在StatefulWidget
上。现在小部件及其状态对象都将引用该列表。当用户向列表中添加项目时,状态对象将其列表替换为从 API 返回的新副本,两者将不同步。即使您这样做了,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(() {});
},
)
);
}
}