前言
上一篇《前端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算法就可以派上用场了,但其实我们还可以做得更多,下一篇我们再来继续探索吧。