场景是这样的:
我们项目里有一个比较复杂的表格20多列每一列里也不是简单把值展示出来,很多还有比较复杂的运算. 应用本身就是一个SPA模式的. 画面的元素很多. 目前这个表格使用Kendo UI的Grid实现的. Kendo的数据绑定是Observable模式的, 你给它一个POJO的对象数组, 它会把这个数组转换为ObservableArray, 表面上看接口跟js的原生数组一样. 但是每当你改变里面的值的时候, 都会触发事件. KendoUI 通过监听这些事件 自动更改页面上的DOM元素, 以实现数据模型和View的同步.
这种方式的好处是可以用最快的速度找到哪些model值发生变化, 不会像AngularJS这种dirty check的那样, 随着页面的绑定的元素增多, 检查model变化的性能会下降.
但是KendoUI对数组类型的元素变化的检查算法太SB了. 只要数组有元素变化, 一律重新生成整个Grid. Grid只有几条的时候还好, Grid中又几百条记录, 并且内容又比较复杂时, 简直慢出翔来了.
相比之下React.js就聪明的多, 会去尽量分析你的变化, 把dom元素的操作降到最低.
从原理看, React.js不是直接操作DOM元素. 它在内存中保持一个virtual dom, 其实就是一个javascript的内存结果, 在javascript上的计算要比dom计算快很多. 最后就是把计算出来的偏差反应到真实dom上.
所以真正牛B的就是这套计算差异的diff算法, 一要高效, 逐个比较肯定不行, 二要精确, 要把需要变化的dom元素降到最低.
具体diff算法的解释, 又一篇很好的文章:
翻译:[url]http://segmentfault.com/a/1190000000606216[/url]
原版:[url]http://calendar.perfplanet.com/2013/diff/[/url]
这里有一个例子, 大家可以自己体验一下. 可以用chrome的F12开发者工具查看dom变化情况.
[url]http://jsfiddle.net/zjumty/w1L01Lbh/1/[/url]
我也试了一下angularjs, add和push不会全更新, 但是把数组中的第一个和最后一个对调就会全更新, 看来diff算法还是不科学啊:
[url]http://jsfiddle.net/zjumty/3fvj63o4/2/[/url]
[b]更新[/b]: React.js也一样: 把数组中的第一个和最后一个对调就会全更新, 为啥会这样 :cry:
[b]更新0430[/b] :
我编了段程序试了一下: [url]http://jsfiddle.net/zjumty/noj0tk2o/[/url]
以下几个场景:
1. 数组最后加一条记录: 最后apppend一个元素
2. 数组第一个元素shift掉: 最后加一条: 第一个元素remove, 最后append
3. 在数组中间插入一个元素: 中间append
4. 把数组第一个元素和最后一个元素对调: 整个table都刷新了, 也就是remove所有的tr, 然后再append
5. 把数组中间一个元素和最后一个元素对调: 后半的tr都remove,然后再append.
按我的理解react.js应该按照key来判断是否一个元素该remove. 在4,5的场景中, 其实中间的元素完全不需要发生变化. 感觉react的diff算法应该还有改进的空间.
部分代码:
var UserTable = React.createClass({
render: function() {
var createItem = function(user) {
return
{user.id} |
{user.name} |
{user.age} |
;
};
return {this.props.users.map(createItem)}
;
}
});
var UserPanel = React.createClass({
getInitialState: function() {
var users = [];
var id = new Date().getTime();
for(var i=0;i<10;i++){
users.push({
id: id + i,
name: "row" + i,
age: i
})
}
return {users: users};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
},
addBtnClick: function(e) {
this.state.users.push({
id: new Date().getTime(),
name: this.refs.name.getDOMNode().value,
age: this.refs.age.getDOMNode().value
});
this.setState({users: this.state.users});
},
pushBtnClick:function(e) {
if(this.state.users.length == 0){
return;
}
this.state.users.shift();
this.state.users.push({
id: new Date().getTime(),
name: this.refs.name.getDOMNode().value,
age: this.refs.age.getDOMNode().value
});
this.setState({users: this.state.users});
},
sortBtnClick:function(e) {
if(this.state.users.length < 2){
return;
}
var f = this.state.users[0];
var l = this.state.users[this.state.users.length - 1];
this.state.users[0] = l;
this.state.users[this.state.users.length - 1] = f;
this.setState({users: this.state.users});
},
sort2BtnClick:function(e) {
var users = this.state.users;
if(users.length < 2){
return;
}
var f = users[users.length / 2];
var l = users[users.length - 1];
users[users.length / 2] = l;
users[users.length - 1] = f;
this.setState({users: users});
},
addMidBtnClick:function(e) {
this.state.users.splice(5, 0, {
id: new Date().getTime(),
name: this.refs.name.getDOMNode().value,
age: this.refs.age.getDOMNode().value
});
this.setState({users: this.state.users});
},
render: function() {
return
;
}
});
React.render(, document.getElementById('container'));