【发布时间】:2025-11-25 09:30:02
【问题描述】:
我的文档和 Flutter 视频,StatefulWidget (+(Widget)State) 的设计解释是这样的:
- 提倡声明式设计(好)
- 正式化了 Flutter 有效决定哪些组件需要重新渲染的过程(也很好)
从例子:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {...}
}
但是:
- 既然我们必须明确记住调用
setState以使状态无效,这真的是声明式设计吗? - Flutter 不会自动检测 State 对象中的更改并决定调用 build(尽管它可能有),因此它并没有真正形式化/自动化/确保视图组件的失效。既然我们必须显式调用
setState,那么Flutter 的(Widget)State/StatefulWidget模式有什么好处,比方说:
class MyHomePage extends StatefulWidget // Define dirty method
{
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
int _counter = 0;
_incrementCounter() {
_counter++;
this.dirty(); // Require the view to be rebuilt. Arranges generateView to be called.
}
@override
Widget generateView(BuildContext context) {return ... rendering description containing updated counter ... ;}
}
...这会给程序员带来同样的标记 UI 脏的负担,同样具有贬义性,并且避免了混淆程序意图的额外抽象。
我错过了什么?在 Flutter 中将 StatefulWidget 与 (Widget)State 分开有什么好处?
[在人们加入 MVC cmets 之前,请注意,Flutter 模型相当明确地只管理小部件的状态,并且它通过 build 方法与 UI 的小部件紧密耦合 - 这里没有关注点分离,也没有对于未附加到视图的更大应用程序状态有很多话要说。]
[还有,版主,这些问题不一样:Why does Flutter State object require a Widget?,What is the relation between stateful and stateless widgets in Flutter?。我的问题是关于目前设计的好处是什么,而不是这个设计是如何工作的。]
更新: @Rémi Rousselet -- 这是一个声明性示例,只需要声明一个新的状态类。通过一些工作,您甚至可以摆脱它(尽管它可能不会更好)。
这种声明与需要交互的方式不需要(用户)声明两个新的循环类型引用类,并且响应状态而改变的小部件与状态解耦(它构造了一个纯函数状态,不需要分配状态)。
这种做事方式无法承受热重载。 (悲伤的脸)。 我怀疑这更多是热重载的问题,但如果有办法让它工作,那就太好了,
import 'dart:collection';
import 'package:flutter/material.dart';
////////////////////////////////
// Define some application state
class MyAppState with ChangeSubscribeable<MyAppState> {
/***
* TODO. Automate notifyListeners on setter.
* Binds changes to the widget
*/
int _counter;
get counter => _counter;
set counter(int c) {
_counter = c;
notifyListeners(); // <<<<<< ! Calls ... .setState to invalidate widget
}
increment() {
counter = _counter + 1;
}
MyAppState({int counter: 0}) {
_counter = counter;
}
}
void main() => runApp(MyApp5());
class MyApp5 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Declare the mutable state.
// Note because the state is not coupled to any particular widget
// its possible to easily share the state between concerned.
// StateListeningWidgets register for, and are notified on changes to
// the state.
var state = new MyAppState(counter: 5);
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: Column(
children: [
// When the button is click, increment the state
RaisedButton(
onPressed: () => {
state.increment(),
print("Clicked. New state: ${state.counter}")
},
child: Text('Click me'),
),
// Listens for changes in state.
StateListeningWidget(
state,
// Construct the actual widget based on the current state
// A pure function of the state.
// However, is seems closures are not hot-reload.
(context, s) => new Text("Counter4 : ${s.counter}"),
),
],
))),
);
}
}
// //////////////////////
// Implementation
// This one is the onChange callback should accept the state.
//typedef OnChangeFunc<ARG0> = void Function(ARG0);
typedef OnChangeFunc = void Function();
mixin ChangeSubscribeable<STATE> {
final _listener2Notifier =
new LinkedHashMap<Object, OnChangeFunc>(); // VoidFunc1<STATE>>();
List<OnChangeFunc> get _listeners => List.from(_listener2Notifier.values);
void onChange(listenerKey, OnChangeFunc onChange) {
// onChange(listenerKey, VoidFunc1<STATE> onChange) {
assert(!_listener2Notifier.containsKey(listenerKey));
_listener2Notifier[listenerKey] = onChange;
print("Num listeners: ${_listener2Notifier.length}");
}
void removeOnChange(listenerKey) {
if (_listener2Notifier.containsKey(listenerKey)) {
_listener2Notifier.remove(listenerKey);
}
}
void notifyListeners() {
// _listener2Notifier.forEach((key, value)=>value(state));
// Safer, in-case state-update triggers add/remove onChange:
// Call listener
_listeners.forEach((value) => value());
}
}
typedef StateToWidgetFunction<WIDGET extends Widget,
STATE extends ChangeSubscribeable>
= WIDGET Function(BuildContext, STATE);
void noOp() {}
class _WidgetFromStateImpl<WIDGET extends Widget,
STATE extends ChangeSubscribeable> extends State<StatefulWidget> {
STATE _state;
// TODO. Make Widget return type more specific.
StateToWidgetFunction<WIDGET, STATE> stateToWidgetFunc;
_WidgetFromStateImpl(this.stateToWidgetFunc, this._state) {
updateState(){setState(() {});}
this._state.onChange(this, updateState);
}
@override
Widget build(BuildContext context) => stateToWidgetFunc(context, this._state);
@override
dispose() {
_state.removeOnChange(this);
super.dispose();
}
}
class StateListeningWidget<WIDGET extends Widget,
STATE extends ChangeSubscribeable> extends StatefulWidget {
STATE _watched_state;
StateToWidgetFunction<WIDGET, STATE> stateToWidgetFunc;
StateListeningWidget(this._watched_state, this.stateToWidgetFunc) {}
@override
State<StatefulWidget> createState() {
return new _WidgetFromStateImpl<WIDGET, STATE>(
stateToWidgetFunc, _watched_state);
}
}
我一直针对 ChangeProvider 模式:https://github.com/flutter/samples/blob/master/provider_counter/lib/main.dart
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Demo Home Page'),),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Consumer<Counter>( // <<< Pure. Hidden magic mutable parameter
builder: (context, counter, child) => Text(
'${counter.value}',
style: Theme.of(context).textTheme.display1,
),),],),),
floatingActionButton: FloatingActionButton(
onPressed: () =>
// <<< Also a hidden magic parameter
Provider.of<Counter>(context, listen: false).increment(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
...但这也有问题:
读者不清楚状态要求是什么或如何提供它们 - 接口(至少在这个 github 示例主页中)示例不需要 Counter 作为形式参数。这里我们有
new HomePage(),其参数中没有提供配置 - 这种类型的访问遇到与全局变量类似的问题。对状态的访问是通过类类型,而不是对象引用 - 因此,如果您想要两个相同类型的对象(例如 shippingAddress、billingAddress),则不清楚(或至少直截了当)该怎么做该模型。为了解决这个问题,可能需要重构状态模型。
【问题讨论】:
-
除了将
setState重命名为dirty和将build重命名为generateView之外,当前的StatefulWidget和您的版本有什么区别? -
额外的抽象是混淆
-
... 没有目的的地方
-
使用您的变体,更改参数(传递给构造函数)将使您的状态 (_counter) 被销毁。您可以通过将道具或状态提取到另一个类中来使其工作,但您又拥有两个类。
-
修改 InheritedWidget 从其后代中是不可行的,使其确实是纯粹的。但那是题外话。您可以使用
ChangeNotifier而不使用InheritedWidget。这就是为什么我之前也提到了AnimatedBuilder。
标签: flutter