前端mvvm框架底层学习(七、双向绑定优化)

前言

上一篇《前端mvvm框架学习(六、最初级的双向绑定)》介绍了我们如何实现一个简单的MVVM对input输入框进行双向绑定的例子,有个历史遗留的问题,就是更新数据会失去焦点。这一篇我们再来优化之前的双向绑定,怎么简单怎么来吧,我们还是引用lodash库和还是用传统的方式拼接html字符串的方式来处理View层吧,还是使用模版函数来处理,直接用lodash的template函数,但在这里,我们引入了高级的玩意,我们引入了snabbdom的全家桶,一个最为流行的virtual dom解决方案。

接着上一篇的代码,我们修改viewModel层的代码


See the Pen MVVM two-way data binding 2 by Nelson Kuang (@nelsonkuang) on CodePen
1

VIEW层代码:

<div id="input-area">
</div>
<!-- View层 end -->

MODEL层代码 :

// --------- Model层 ----------
var newPerson = {
  myInput: "dfdf"
};

VIEWMODEL层代码:

// --------- viewModel层 ----------
// console.log(window)
var snabbdom = window.snabbdom; //定义patch函数 实现dom节点更新的核心方法
var patch = snabbdom.init([
  snabbdom_class,
  snabbdom_props,
  snabbdom_style,
  snabbdom_eventlisteners
]);
var h = window.h; // 定义h函数
var toVNode = window.tovnode.default; // 定义toVNode函数
var oldVNode = null,
  newVNode = null;
function bindInputModel() {
  var inputEl = document.querySelector("#my-input");
  if (inputEl && inputEl.value !== undefined) {
    inputEl.oninput = function(e) {
      console.log(newPerson.myInput);
      if (e.target.value !== newPerson.myInput) {
        newPerson.myInput = e.target.value;
      }
    };
  }
}
function updateInputArea(newVal, oldVal) {
  var el = document.querySelector("#input-area");
  // 传统的拼接html字符串的方式实现更新视图View
  var compiled = _.template(
    '<input type="text" class="form-control" data-hotkey="xxxhotkey" name="search" value="${ myInput }" placeholder="Search or jump to…" id="my-input" /><span>The value of the input is <b id="my-input__val">${ myInput }</b></span>'
  );
  var inputEl = el.querySelector("#my-input");
  if (!inputEl) {
    el.innerHTML = compiled({ myInput: newVal });
    oldVNode = toVNode(el);
  bindInputModel();
  } else { // 关键使用Virtual Dom处理代码如下:
    var newNode = el.cloneNode(false)
    newNode.innerHTML = compiled({ myInput: newVal });
    newVNode = toVNode(newNode)
    patch(oldVNode, newVNode)
    oldVNode = newVNode
  }
}
Object.defineProperty(newPerson, "myInput", {
  set: function(x) {
    if (x !== this.oldPropValue) {
      updateInputArea(x, this.oldPropValue);
      this.oldPropValue = x;
    } else {
      console.log("myInput的值没变");
    }
  },
  get: function() {
    console.log("in property get accessor");
    return this.oldPropValue;
  },
  enumerable: true,
  configurable: true
});
bindInputModel();

为什么使用Virtual Dom的Patch&diff算法来更新Dom呢,因为我们如果直接使用el.innerHTML = XX 的方式来更新View视图层的话是repaint全部重绘的方式,必然会导致input框失去焦点,而使用Virtual Dom的Patch的方式使得我们可以不必repaint重绘的方式来更新View视图层的Dom节点;Model数据层的变化,只会触发相应的视图层的Dom节点的某些属性值的变化,不必进行相应的Dom树的重绘,因而解决了失去焦点的问题。

现在尝试更新MODEL层数据,或者随意修改input输入框内容应该可以看到相应显示的值会随之而变了

setTimeout(function() {
  newPerson.myInput = "新的值"; // set的方式改变input框的值,或者自己手动去修改input框的值也可以
}, 5000);

结束语

通过上面的demo我们可以知道,要实现双向绑定是需要给input框绑上oninput的方法,然后把变化的值传回给model数据,这种做法是比较正路的方法,但却带出了一个新的问题,就是每次编辑都会失去了焦点,这当然是不能接受的,要解决这个,痛点,其实就是解决局部更新的问题,或者解决更小颗粒度更新的问题,这时Virtual Dom的Diff与Patch算法就可以派上用场了,但其实我们还可以做得更多,下一篇我们再来继续探索吧。

 

作者: 博主

Talk is cheap, show me the code!

发表评论

邮箱地址不会被公开。

Captcha Code