做爲一個開發人員,選擇一款得心應手的開發框架對提高生產效率和愉悅編碼體驗是尤爲重要的。近兩年我從後端開發轉向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變更大概需要如下幾個步驟
- 創建一個從
StatefulWidget
繼承的類 - 創建一個對應的
Widget
的 state 類 - 編寫 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
類似的創建屬性的方法還有propertyAdaptive
、propertyAsync
用法詳見源碼中示例
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端開發的距離呢?快來嘗試一下吧,期待你的答案。。