从web端开发到app端开发也许只有一个Flutter MVVM的距离

做为一个开发人员,选择一款得心应手的开发框架对提高生产效率和愉悦编码体验是尤为重要的。近两年我从后端开发转向web端开发,开发重心也由之前数据层面转变为现今的展现层面。web端有很多出色的开发框架,Vue.js、React、Angular、Ember.js 等等都深受广大web端开发的喜爱,也都是非常不错的选择,挑来选去在众多开发框架中选择了Angular。

 

Angular

只所以选择Angular是因为Angular中的很多理念对一个曾经的后端开发来说并不陌生,甚至还会带有几分亲切,Module、依赖注入、守卫、provider等等,每一项都那么熟悉。但是,除了这些最能吸引到我的更是她优雅的数据绑定功能,Angular的数据绑定“语法”非常简洁、明了,就算是新手,扫几眼也就懂了。使用起来也非常方便,几乎就像操作js原生对象,简单易懂。

Angular中的数据绑定
<h2>{{ title }}</h2>
<div *ngIf="status === 1">hello world!</div>

数据绑定是Angular的核心,它可以大大简化开发人员对页面dom树控制的复杂度,使我们可以从原本复杂的页面操控中解脱出来。其实不光是Angular,Vue.js、React、Ember.js 等框架也都有类似实现,业界将这种通过数据绑定驱动视图变化的模式称之为 MVVM(https://zh.wikipedia.org/wiki/MVVM)。

 

Flutter

要构建一个Flutter app,大部分工作就是构建各种widget树(UI),widget树结构其实很像html里的dom树,对于web端开发人员来说并不难理解。但是上手之后就会发现,对于widget树的操控仿佛回到了html中的getElementById时代,即使是很简单的widget变更操作,也都需要开发人员手动管理非常多的逻辑代码。如果碰到多个widget变更的复杂场景,摆在开发人员面前的必然是更加繁重的工作量。如果要终日以这样的方式写Flutter代码,估计我撑不过三天,这对于我这样懒惯了的Angular开发来说是没法接受的!〜

Flutter中实现widget变更大概需要如下几个步骤
  1. 创建一个从StatefulWidget继承的类
  2. 创建一个对应的 Widget 的 state 类
  3. 编写 widget 变更逻辑,并最终使用 setState() 更新到UI

 

Flutter MVVM

既然Flutter中的widget树和html里的dom树很像,而web端各大框架又都能以数据绑定(MVVM)来简化对dom树的控制操作,那么 在Flutter中能不能像web框架那样通过数据绑定(MVVM)来简化对widget树的操控呢? 带着这样的疑问,在我家二狗还没睡醒的清晨,尝试着实现了一个Flutter的MVVM。

 

它大概可以帮你解决Flutter开发过程中如下几个问题

  • 我有选择困难症,我不想选择用StatelessWidget还是StatefulWidget。
  • 我是一个懒人,当我变更widget(UI显示)时,我不想每次都手动 setState
  • 我有洁癖,当我开发功能时,我希望各个环节职能能够更加清晰。
  • 最重要的,我希望我能从复杂的widget树结构控制中解脱出来。

 

 

什么?说了这么多,不如看几行代码?

 

在项目中使用她需要如下几个步骤

1. 在项目中添加依赖

找到项目中 pubspec.yaml 文件, 并在 dependencies 部分加入下面内容

mvvm: ^0.1.3

 

2. 添加包引用

在代码页中加入

import 'package:mvvm/mvvm.dart';

 

3. 创建视图模型(ViewModel)

视图模型类需从 ViewModel 类继承, 并在构造方法中使用 propertyValue 方法创建需要绑定支持的属性

import 'package:mvvm/mvvm.dart';
import 'dart:async';

// define ViewModel
class Demo1ViewModel extends ViewModel {
  Demo1ViewModel() {
    // define bindable
    propertyValue<DateTime>("time", initial: DateTime.now());
    // start timer
    start();
  }

  start() {
    Timer.periodic(const Duration(seconds: 1), (_) {
      // call setValue
      setValue<DateTime>("time", DateTime.now());
    });
  }
}
propertyValue 类似的创建属性的方法还有 propertyAdaptivepropertyAsync 用法详见源码中示例

 

4. 创建视图(View)

视图类需从 View 类继承, 并指定使用刚刚创建的视图模型。重写 Widget BuildCore(BuildContext) 方法,并在方法内使用 $ (ViewContext) 和 $Model (ViewModel) 辅助属性构建视图 Widget

import 'package:mvvm/mvvm.dart';
import 'package:flutter/widgets.dart';

// define View
class Demo1 extends View<Demo1ViewModel> {

  // call super
  Demo1() : super(Demo1ViewModel());

  @override
  Widget buildCore(BuildContext context) {
    return Container(
        margin: EdgeInsets.symmetric(vertical: 100),
        padding: EdgeInsets.all(40),

        child: Column(children: [
          // binding
          $.watchFor("time",
              builder: $.builder1((t) => Text(
                  "${t.hour}:${t.minute}:${t.second}",
                  textDirection: TextDirection.ltr))),
          // binding
          $.$ifFor("time",
              builder: $.builder0(
                  () => Text("hello world!", textDirection: TextDirection.ltr)),
              valueHandle: (t) => t.second % 2 == 0)
        ]));
  }
}
$.watchFor(..) 类似的还有 $.watch(..)$.if(..)$.cond(..)$.condFor(..)$.switch(..)$.switchFor(..)等,后续还可以扩展更多,用法详见源码中示例

 

5. 应用视图

// run
void main() => runApp(Demo1());

 

web端开发的小伙伴们是不是觉的 $.watchFor(..)$.ifFor(..) 等语法能更亲切一些呢?其实这些就是前边提到的数据绑定语法的类似实现了。

在这个Flutter的MVVM实现中,我们将原有混在一起的widget树和逻辑数据,拆分为视图逻辑(ViewModel)与视图展示(View)两部分,并通过数据绑定在两者之间建立关联,最终用视图逻辑(ViewModel)结合数据绑定器驱动视图(widget树)变化。可以看到示例中我们在 View 类中能更加专注的处理视图展示,所有与视图逻辑相关的数据操作都由 ViewModel 来管理,在职责上有着清晰的划分界限。

 

最后

MVVM模式由来已久,在很多展现层框架中都有应用,并且深受开发人员喜爱。这个Flutter的实现还很简陋,还有很多可以扩展的地方,但相信她能越来越完善,也希望她能给你带来编码的快乐,感谢阅读!

 

她是不是能拉近你与app端开发的距离呢?快来尝试一下吧,期待你的答案。。

 

相关链接

相关推荐