react中虚拟算法有两种方式
1,jsx语法
var virtualDom1=<div>这是jsx语法实现的虚拟dom</div>
2,React.createElement()
var virtualDom2=React.createElement('ul',{className:"ul-list"},[
React.createElement('li',{},["a"]),
React.createElement('li',{},["b"]),
React.createElement('li',{},["c"])]
);
ReactDOM.render(virtualDom2,root)
react内部会自动将jsx转化为createElement方式的虚拟dom
实现react的虚拟dom
下面是用原生js模拟react中的createElement以及render实现虚拟dom。
myCreateElemnt是模拟react中的createElement:
var virtualDom=myCreateElemnt('ul',{className:"ul-list"},[
myCreateElemnt('li',{},["a"]),
myCreateElemnt('li',{},["b"]),
myCreateElemnt('li',{},["c"])]
);
让我们看看myCreateElemnt()的实现吧:
把每一个节点封装为一个Element对象:
function Element(type,props,children) {
this.type=type;
this.props=props;
this.children=children;
}
//创建虚拟dom的实现
function myCreateElemnt(type,props,children){
return new Element(type,props,children);
}
这样一个简单的虚拟dom就实现了,是不是很简单,其实虚拟dom就是js对象。
ReactDOM.render()方法会将虚拟dom渲染为真实dom,然后会将真实的dom添加到页面上进行展示,这里可以分为两步走:
第一步:将真实dom转换为真实dom
//虚拟dom渲染为真实dom
var actualDom=createActualDom(virtualDom);
//创建真实dom的实现
function createActualDom(virtualDom){
var actualDom=document.createElement(virtualDom.type);
for(var prop in virtualDom.props){
setAttr(actualDom,prop,virtualDom.props[prop]);
}
virtualDom.children.forEach(child=>{
if(child instanceof Element){
var el=createActualDom(child);
actualDom.appendChild(el);
}else{
var el=document.createTextNode(child);
actualDom.appendChild(el);
}
})
return actualDom;
}
第二步: 将真实dom节点渲染到页面上:
var targetDom=document.getElementById("root");
//将真实dom渲染到页面上
renderDom(actualDom,targetDom);
function renderDom(actualDom,targetDom){
targetDom.appendChild(actualDom);
}
好了,这样从虚拟dom到页面真实dom的实现,我们已经实现了~~
diff算法实现
diff算法,主要是比较两个虚拟dom的差异,然后将差异更新到真实dom上面。
diff函数传入两个虚拟dom.然后返回比较的差异对象。
var patches=diff(virtualDom,virtualDom2);
function diff(virtualDom,virtualDom2){
var patches={};
//比较
walk(virtualDom,virtualDom2,index,patches);
return patches;
}
walk是比较两个虚拟dom,然后层层递归实现层层之间的dom比较。
//只是两个节点的属性比较而已
function walk(virtualDom,virtualDom2,index,pathches){
var currentPatch=[];
var attrs;
if(!virtualDom2){
currentPatch.push({type:'REMOVE',index})
}else if(Object.prototype.toString.call(virtualDom)==='[object String]' && Object.prototype.toString.call(virtualDom2)==='[object String]'){
currentPatch.push({type:'TEXT',text:virtualDom2})
}else if(virtualDom.type===virtualDom2.type){
attrs=diffAttr(virtualDom.props,virtualDom2.props);
if(Object.keys(attrs).length>0){
currentPatch.push({type:'ATTR',attrs});
//比较子节点
}
diffChildren(virtualDom.children,virtualDom2.children,pathches);
}else{
//节点类型不同,直接替换
currentPatch.push({type:'REPLACE',newNode:virtualDom2});
}
if(currentPatch.length>0){
pathches[index]=currentPatch;
}
return pathches;
}
//比较节点的children
function diffChildren(oldChildren,newChildren,pathches){
oldChildren.forEach((child,inde)=>{
walk(oldChildren[inde],newChildren[inde],++index,pathches);
})
}
//两个节点的属性比较
function diffAttr(oldProps,newProps,index,patches){
var attrsPatch={};
//遍历老节点的属性进行修改
for(let key in oldProps){
if(oldProps[key]!==newProps[key]){
attrsPatch[key]=newProps[key];
}
}
//对新增节点但是没有老节点的属性进行修改
for(let key in newProps){
if(!oldProps.hasOwnProperty(key)){
attrsPatch[key]=newProps[key];
}
}
return attrsPatch;
}
现在,我们已经获取到两个虚拟dom时间不同的地方,接下来,我们需要将这些变化也就是patches对象更新到真实dom上。
//将补丁渲染到真实dom上面
doPatch(actualDom);
function doPatch(node){
var currentNodePatches=patches[index2++];
var children=node.childNodes;
children.forEach((child=>{
doPatch(child);
}))
if(currentNodePatches){
currentNodePatches.forEach(patch=>{
switch (patch.type) {
case 'REPLACE':
var newNodeActualDom= patch.newNode instanceof Element?createActualDom(patchnewNode):document.createTextNode(patch.newNode);
node.parentNode.replaceChild(newNodeActualDom,node);
break;
case 'REMOVE':
node.parentNode.removeChild(node);
break;
case 'TEXT':
node.textContent=patch.text;
break;
case 'ATTR':
for(let key in patch.attrs){
var value=patch.attrs[key];
if(value){
setAttr(node,key,value)
}else{
node.removeAttribute(key)
}
}
break;
default:
break;
}
})
}
}
这样虚拟dom的diff算法就完成了,虽然代码很简短,但是关键是逻辑要能够理解清楚,建议初学者跟着代码多敲几遍哈哈·~~~~