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

时间:2019-09-15 16:43:11

标签: flutter

我的文档和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模型只显式地仅管理小部件的状态,并且通过build方法将其紧密耦合到UI的小部件-这里没有分离的关注点,并且没有关于未附加到视图的更大的应用程序状态,还有很多话要说。]

[主持人,这些问题也不尽相同:Why does Flutter State object require a Widget?What is the relation between stateful and stateless widgets in Flutter?。我的问题是关于当前设计的好处是什么,而不是该设计的工作原理。]

更新: @RémiRousselet-这是一个声明性示例,仅需要声明一个新的状态类。通过一些工作,您甚至可以摆脱它(尽管可能不会更好)。

这种声明与需求交互的方式不需要(用户)声明两个新的循环类型引用类,并且响应状态而变化的小部件与状态分离(它构造了一个纯函数)状态,不需要分配状态。

这种做事方式无法在热重装中幸存下来。 (悲伤的脸)。 我怀疑这与热重装有关,但如果有一种方法可以使它工作,那就太好了,

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)作为对等对象,则不清楚如何(至少是直接做到)该模型。要解决此问题,可能需要重构状态模型。

1 个答案:

答案 0 :(得分:0)

我认为与此有关。 (顺便说一句)。 不幸的是,Flutter的作者似乎在View类后缀了“ State”一词。这使整个Flutter状态管理讨论变得很混乱。

我认为这两个类的目的实际上是为了使绘画表现得更好,但是这对我们的开发人员来说是非常沉重的管道成本。

关于命名约定: 脏标志方法允许小部件画家在不了解我们状态的情况下优化其绘画,从而减轻了对两个类的需求。 generateView()也是有意义的(当然,除非您开始使用这些小部件来保存模型片段(根据Package:provider)。