Flutter的(Widget)State/StatefulWidget模式的设计优势是什么?



我的文档和 Flutter 视频,对StatefulWidget设计(+(Widget)State(的解释是:

  1. 促进声明性设计(好(
  2. 形式化 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) {...}
}

然而:

  1. 既然我们必须明确记住调用setState才能使状态无效,这真的是一个声明式设计吗?
  2. 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 评论之前,请注意,Flutter 模型相当明确地只管理小部件的状态及其通过构建方法与 UI 的 Widget 紧密耦合 - 这里没有分离关注点,对于未附加到视图的较大应用程序状态,它没有太多要说的。

[另外,版主们,这些不是同一个问题:为什么 Flutter State 对象需要 Widget?,Flutter 中的有状态和无状态 widget 之间的关系是什么?。我的问题是关于当前设计的好处是什么,而不是这种设计如何工作。

更新:@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(在模型中是对等方,它不清楚(或至少是直截了当的(该怎么做。若要解决此问题,可能需要重构状态模型。

我想我和user48956在一起。(顺便说一下,这个名字朗朗上口(。 不幸的是,Flutter 的作者似乎在他们的 View 类后加上了"State"这个词。这让整个 Flutter 状态管理讨论相当混乱。

我认为这两个类的目的实际上是使绘画更具性能,但它给我们开发人员带来了非常沉重的管道成本。

至于命名约定: 脏旗方法允许小部件绘制者在不知道我们的状态的情况下优化他们的绘画,从而减轻了对两个类的需求。 此外,generateView((有点有意义(当然,除非您开始使用这些小部件来保存模型片段(根据Package:provider(。

最新更新