mvvm中 viewmodel該如何設計?
如題
ViewModel的設計就一個準則,就是把所有放在UI裡面的邏輯(也就是不在Model裡面的東西),和UX強加給你的藝術表現手法無關(譬如說按鈕要飛一個圈之類的)的部分,全部寫在ViewModel裡面,從而不需要啟動GUI就可以進行所有測試,最後GUI的部分只是為了畫圖。
因此ViewModel就是跟GUI的樣子綁定在一起的,GUI改了ViewModel跟著改,ViewModel也基本不打算在多個GUI之間共享代碼。當然了,你可以在Windows和Mac上同一個窗口裡共享ViewModel,因為這畢竟是同一個GUI,只是【畫圖的部分實現了兩次】。以下答案是WPF的, Android不是很清楚,我猜會有類似功能的東西。
ViewModel是View和Model之間的中介, 一般包含Property和Command.
Property可以來自Model, 根據View的要求,你可以選擇那些Model的property會被暴露出來,綁定到UI上。
Property也可以是computed property, 對Model的property的一種組合,一個簡單的例子是FullName, 可能Model里只有FirstName和LastName.
一些View相關的property也可以放在ViewModel里,比如某個TextBox的enable/disable狀態是由另外的property通過一些計算來得到的。用Converter的話要新建一個class, 並且為這個converter做單元測試又更加麻煩。
Command的話,常見的有DelegateCommand(Prism)和RelayCommand(Mvvmlight), 更加喜歡Prism的, 允許listen某個property change來改變command的狀態。如果實現自己的ICommand的時候要注意避免使用CommandManager.RequerySuggested,有時會有性能問題。
一些event handler也可以放在ViewModel里,可以通過EventToCommand(Mvvmlight)或者InvokeCommandAction(System.Windows.Interactivity)之類的。正在寫一個 Javascript MVVM 庫
GitHub - kyleslight/LiKy: A Javascript MVVM Library for Building Interface. (副標題應該改成 Yet Another Javascript MVVM Library,這是一個個人用於學習的實驗項目,目前代碼體積非常小,歡迎 star 與 fork (〃 ̄ω ̄〃ゞ(當然我這裡主要說的是 Web 的 VM 實現)
1. VM 做了什麼
@尤雨溪 大大在 Overview - vue.js 的一張圖可以非常清晰地解釋 VM 做了什麼:
- 從 M 到 V 的映射(Data Binding),這樣可以大量節省你人肉來 update View 的代碼(也就是 @vczh 所說的 UI 邏輯)
- 從 V 到 M 的事件監聽(DOM Listeners),這樣你的 Model 會隨著 View 觸發事件而改變
所以問題的核心收斂於上面兩條如何實現。
2. VM 如何實現
2.1 M 到 V
做到這件事的第一步是形成類似於:
{{ text }}& This is some text&// template
var tpl = "&
// data
var data = {
text: "This is some text"
};
// magic process
template(tpl, data); // "&
- JavaScript templates
- JavaScript template engine in just 20 lines
- 一個 JavaScript 模板引擎的實現
無論是 Angular 的 $scope,React 的 state 還是 Vue 的 data 都提供了一個較為核心的 model 對象用來保存模型的狀態;它們的模板引擎稍有差別,不過大體思路相似;拿到渲染後的 string 接下來做什麼不言而喻了(中間還有很多處理,例如利用 model 的 diff 來最小量更新 view )。
但是僅僅是這樣並不夠,我們需要知道什麼時候來更新 view( 即 render ),一般來說主要的 VM 做了以下幾種選擇:
- VM 實例初始化時
- model 動態修改時
其中初始化拿到 model 對象然後 render 沒什麼好講的;model 被修改的時候如何監聽屬性的改變是一個問題,目前有以下幾種思路:
- 藉助於 Object 的 observe 方法
- 自己在 set,以及數組的常用操作里觸發 change 事件( 參考 @宗宇 大大的這篇答案 2015百度前端學院中有關實現一個mvvm表單的任務? - 宗宇的回答)
- 手動 setState(),然後在裡面觸發 change 事件
知道了觸發 render 的時機以及如何 render,一個簡單的 M 到 V 映射就實現了。
2.2 V 到 M
從 V 到 M 主要由兩類( 雖然本質上都是監聽 DOM )構成,一類是用戶自定義的 listener, 一類是 VM 自動處理的含有 value 屬性元素的 listener
第一類類似於你在 Vue 里用 v-on 時綁定的那樣,VM 在實例化得時候可以將所有用戶自定義的 listener 一次性代理到根元素上,這些 listener 可以訪問到你的 model 對象,這樣你就可以在 listener 中改變 model
第二類類似於對含有 v-model 與 value 元素的自動處理,我們期望的是例如在一個輸入框內
&
輸入值,那麼我與之對應的 model 屬性 message 也會隨之改變,相當於 VM 做了一個默認的 listener,它會監聽這些元素的改變然後自動改變 model,具體如何實現相信你也明白了 :)
3. Afterword
一個簡單的 VM 大致設計如此,當然一些流行的 MVVM Library 還有更多特性(諸如 component , filter,指令集,數據持久化 etc.),具體實現起來也是有非常多的坑的(特別是你想兼容早期的瀏覽器 =_=)
最後祝你身體健康,入坑大成功,以上。實現 ViewModel 就需要將數據模型(Model)和視圖(View)關聯起來,整個思路可以簡單總結成 5 點吧:
實現一個 Compiler 對元素的每個節點進行指令的掃描和提取;
實現一個 Parser 去解析元素上的指令,能夠把指令的意圖通過某個刷新函數更新到 dom 上(中間可能需要一個專門負責視圖刷新的模塊)比如解析節點 &
&
時先取得 Model 中 isShow 的值,再根據 isShow 更改 node.style.display 從而控制元素的顯示和隱藏;實現一個 Watcher 能將 Parser 中每條指令的刷新函數和對應 Model 的欄位聯繫起來;
實現一個 Observer 使得能夠對對象的所有欄位進行值的變化監測,一旦發生變化時可以拿到最新的值並觸發通知回調;
利用 Observer 在 Watcher 中建立一個對 Model 的監聽 ,當 Model 中的一個值發生變化時,監聽被觸發,Watcher 拿到新值後調用在步驟 2 中關聯的那個刷新函數,就可以實現數據變化的同時刷新視圖的目的。
與View成對設計,負責關聯Model和處理ui邏輯,是界面的非展現部分。
來自微軟官方的回答(MSDN上也有其他文字回答,都差不多,隨便拿了個來):
用mvvm ligtht,輕量級,有消息,封裝mvvm基本理念,開源,用起來會好一些:)
ViewModel就是對View的抽象,包括數據和部分行為。數據的抽象在ViewModel中的體現就是依賴項屬性,行為的抽象在ViewModel中就是命令。你要說怎麼設計的話就看界面中都有哪些數據,有哪些行為,然後在對應的ViewModel中添加相應的屬性和命令。另外對於業務邏輯我個人傾向於聚合到Model下,對於難以聚合的寫成Service或XXXHelper的形式。
推薦閱讀:
※如何正確使用Vue.js的組件?
※AngularJS 有沒有缺點?MVVM 框架中有比它更好的嗎?
※React中的virtual dom是否可以理解為當前組件的view model?