[開源] 從web端開發到app端開發也許只有一個Flutter MVVM的距離

unicreators發表於2019-09-03

做為一個開發人員,選擇一款得心應手的開發框架對提高生產效率和愉悅的編碼體驗是尤為重要的。近兩年我從後端開發轉向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(zh.wikipedia.org/wiki/MVVM)。

 

Flutter

要構建一個Flutter app,大部分工作就是構建各種widget樹(UI),widget樹結構其實很像html裡的dom樹,對於web端開發人員來說並不難理解。但是上手之後就會發現,對於widget樹的操控彷彿回到了html中的getElementById時代,即使是很簡單的widget變更操作,也都需要開發人員手動管理非常多的程式碼邏輯,如若碰到多個widget變更的複雜場景,擺在開發人員面前的必然是更加繁重的程式碼工作量。這對於我這樣懶慣了的Angular開發來說是沒法接受的!〜

Flutter中實現widget變更大概需要如下幾個步驟

  1. 建立一個從StatefulWidget繼承的類
  2. 建立一個對應的 Widget 的 state 類
  3. 編寫變更邏輯,並最終使用 setState() 更新到UI

 

Flutter MVVM

既然Flutter中的widget樹和html裡的dom樹很像,而web端各大框架又都能以資料繫結來簡化對dom樹的控制操作,那麼 在Flutter中能不能像web框架那樣通過資料繫結來簡化對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端開發的距離呢?快來嘗試一下吧。。

 

相關連結

相關文章