我個人以為mvvm框架裡面最重要的一點就是VM這部分,它要與Model層建立聯絡,將Model層轉換成可以被View層識別的資料結構;其次也要同View建立聯絡,將資料及時更新到View層上,並且響應View對資料的更改,同步到Model層。
MVVM的具體例子,可以看一下阮一峰老師的這篇部落格。
我們提取其中比較關鍵的點:
- Model層儲存資料
- 需要一個View-Model來對資料做中轉,響應資料變化,同步到兩端
- View層來負責展示資料,接受使用者事件
Model層,我們用一個物件來代表。例如:
let data = {
text: `foo`
};
View層對於我們而言,可以認為是DOM節點。例如:
<div id="app">
<p>text</p>
</div>
為了方便注入內容,改用JS來寫,可以寫成
let str = `<div id="app"><p>test</p></div>`;
至於View-Model,我們要做兩件事,一是將資料及時同步到View層,二是響應使用者事件,更改資料。我們設計一個函式來完成這項工作。
// 把物件轉成可監聽的
const ob = function (data) {
// 無new構造
if (!(this instanceof ob)) {
return new ob(data);
}
// 設定觀察者列表
let observerList = [];
// 暴露新增觀察者方法
this.addOb = function (fn) {
observerList.push(fn);
}
// 遍歷屬性,通過defineProperty來對屬性變化做監聽
for (let key in data) {
let value = data[key];
Object.defineProperty(data, key, {
enumerable:true, // 列舉
get () {
return value;
},
set (newVal) {
value = newVal;
observerList.forEach((el)=>{
el(newVal);
})
}
})
}
};
為了簡單起見,把View的渲染封裝成一個函式,當然實際上不能這麼操作。
const render = (text) => {
document.getElementById(`app`).innerHTML = `<p>`+ text +`</p>`;
}
然後將render和資料繫結起來。
let testObj = {
name: `liu`
};
const vm = (data) => {
render(testObj.name);
let newOb = ob(data);
newOb.addOb(function () {
render(data.name);
})
};
vm(testObj);
// 如果直接設定testObj.name = `test`;就會觸發對應的修改
以上,基本上實現了資料和檢視間的繫結。
可以再繼續改進,在render之前,加入virtual dom的邏輯,或者加入一些語法特性,比如類似VUE和React的語法糖。
由於直接設定物件屬性其實不大安全,而且不易於追蹤,可以把狀態統一提取出來,進一步封裝,只能通過action去觸發修改,然後分發到各個呼叫方。保證資料的單項流動。
這裡是一個簡單的例子。
感謝閱讀。