擼一個簡單的MVVM例子

liuyongjia發表於2018-06-27

我個人以為mvvm框架裡面最重要的一點就是VM這部分,它要與Model層建立聯絡,將Model層轉換成可以被View層識別的資料結構;其次也要同View建立聯絡,將資料及時更新到View層上,並且響應View對資料的更改,同步到Model層。
MVVM的具體例子,可以看一下阮一峰老師的這篇部落格
我們提取其中比較關鍵的點:

  1. Model層儲存資料
  2. 需要一個View-Model來對資料做中轉,響應資料變化,同步到兩端
  3. 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去觸發修改,然後分發到各個呼叫方。保證資料的單項流動。
這裡是一個簡單的例子
感謝閱讀。

相關文章