在Flutter中分离UI和逻辑

3
通常,我使用一个单独的类,在小部件的顶部声明一个对象。我想知道这种架构存在什么问题。
我在Flutter中发现了一个完整的包,WidgetView,需要声明一个依赖项,然后创建一个状态对象,然后再做同样的事情。
为什么不使用一个简单的类来实现相同的功能,就像下面这样?
class NewAccountComponent extends StatelessWidget {  
final NewAccountComponentLogic logic = NewAccountComponentLogic();
  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Enter a Unique Account Number'),
      titlePadding: EdgeInsets.all(20.0),
      content: TextFormField(
        controller: logic.controller,
            onPressed: () => logic.clearTextFormField(),
          ),
        ),
}
class NewAccountComponentLogic {
  static String accountNumber;
  static bool existsAccountNumber;
  TextEditingController controller = TextEditingController();
  clearTextFormField() {
    controller.text = '';
    accountNumber = '';
}
2个回答

9

您可以以多种方式分离小部件逻辑和表现形式。其中一种方法(您提到的一种方法)是使用WidgetView模式。您可以在不依赖于任何其他库的情况下完成此操作:

  1. 创建一个包含所有WidgetViews都应该实现的逻辑的抽象类:

对于无状态小部件:

abstract class StatelessView<T1> extends StatelessWidget {
  final T1 widget;
  const StatelessView(this.widget, {Key key}) : super(key: key);
  
  @override
  Widget build(BuildContext context);
}

对于有状态的小部件:

abstract class WidgetView<T1, T2> extends StatelessWidget {
  final T2 state;
  T1 get widget => (state as State).widget as T1;

  const WidgetView(this.state, {Key key}) : super(key: key);
  
  @override
  Widget build(BuildContext context);
}
  1. 正常创建您的小部件:
// Note it's a StatefulWidget because accountNumber mutates
class NewAccountComponent extends StatefulWidget {
  @override
  _NewAccountComponentState createState() => _NewAccountComponentState();
}

class _NewAccountComponentState extends State<NewAccountComponent> {
  String accountNumber;
  bool existsAccountNumber;
  final TextEditingController controller = TextEditingController();

  clearTextFormField() {
    controller.text = '';
    accountNumber = '';
  }

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Enter a Unique Account Number'),
      titlePadding: EdgeInsets.all(20.0),
      content: TextFormField(
        controller: controller,
        onSaved: (value) => clearTextFormField(),
      ),
    );
  }
}
  1. 如果小部件是一个 有状态的(Stateful) 小部件
class NewAccountComponent extends StatefulWidget {
  @override
  _NewAccountComponentController createState() => _NewAccountComponentController();
}

// State suffix renamed to Controller
// This class has all widget logic
class _NewAccountComponentController extends State<NewAccountComponent> {
  String accountNumber;
  bool existsAccountNumber;
  final TextEditingController controller = TextEditingController();

  clearTextFormField() {
    controller.text = '';
    accountNumber = '';
  }

  // In build, returns a new instance of your view, sending the current state
  @override
  Widget build(BuildContext context) => _NewAccountComponentView(this);
}

// View extends of WidgetView and has a current state to access widget logic
// with widget you can access to StatefulWidget parent
class _NewAccountComponentView
    extends WidgetView<NewAccountComponent, _NewAccountComponentController> {

  _NewAccountComponentView(_NewAccountComponentController state): super(state);

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Enter a Unique Account Number'),
      titlePadding: EdgeInsets.all(20.0),
      content: TextFormField(
        controller: state.controller,
        onSaved: (value) => state.clearTextFormField(),
      ),
    );
  }
}

如果它是无状态的,将其从以下内容更改为:
class MyStatelessWidget extends StatelessWidget {
  final String textContent = "Hello!";

  const MyStatelessWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(textContent),
    );
  }
}

to:

// Widget and logic controller are unit
class MyStatelessWidget extends StatelessWidget {
  final String textContent = "Hello!";

  const MyStatelessWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) => _MyStatelessView(this);
}

// The view is separately
class _MyStatelessView extends StatelessView<MyStatelessWidget> {
  _MyStatelessView(MyStatelessWidget widget) : super(widget);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(widget.textContent),
    );
  }
}

参考:

Flutter: WidgetView - 一种简单的布局和逻辑分离方法


1
谢谢您的详细解释。但是为什么不用一个简单的类就可以完成任务呢?为什么我需要扩展无状态的小部件来处理逻辑呢? - undefined
当然,将其分离为一个包含所有逻辑的单一类的想法是有效的。我只是展示了一种方法,并遵循了该模式的规则: 每个状态(或StatelessWidget)都有一个子WidgetView,其中包含声明性视图代码。State作为WidgetView的Controller / Mediator / Presenter的替代品,响应视图事件并提供对状态的访问。WidgetView只是一个纯布局的StatelessWidget。 - undefined
你可以采用更适合问题的方法。对于逻辑较少的小部件,我认为没有必要分成多个部分,更不用说应用我提到的模式了。 - undefined
非常感谢。我现在对这个问题非常清楚。 - undefined

2

我稍微修改了你的代码。如果你将代码更改为以下代码,希望你能得到预期的输出。

class NewAccountComponent extends StatelessWidget {
  final NewAccountComponentLogic logic = NewAccountComponentLogic(
    '123456',
    true,
    TextEditingController(),
  );
  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Enter a Unique Account Number'),
      titlePadding: EdgeInsets.all(20.0),
      content: TextFormField(
        controller: logic.controller,
        
      ),
      actions: <Widget>[
          TextButton(
            child: Text('Done'),
            onPressed: () {
              print(logic.controller.text);
              logic.clearTextFormField();
            },
          ),
        ],
    );
  }
}

class NewAccountComponentLogic {
  String accountNumber;
  bool existsAccountNumber;
  TextEditingController controller;
  NewAccountComponentLogic(
    this.accountNumber,
    this.existsAccountNumber,
    this.controller,
  );

  void clearTextFormField() {
    controller.text = '';
    accountNumber = '';
  }

@Ignacior也给出了一个不错的解决方案,您可以按照他的方式操作。

这是一个很好的简洁解决方案,非常类似于getx控制器。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接