React vertual dom实现源码

最近一直在研究react 虚拟dom、diff 算法。发现其实虚拟dom也没有想象中那么难,只要抓住几个要点,掌握它的实现流程、原理,那么用代码来实现就简单多了。

以下全为个人见解,有遗漏或不正确的希望友友们及时指出

  • 创建虚拟dom树
  • 将新产生的虚拟dom树与上一次的虚拟dom树进行比较,产生patch补丁包
  • 将补丁包更新到真实dom树
  • 将dom挂载到页面某元素下面
  1. 首先再来回顾一下,react虚拟dom的意义:

为了解决浏览器的性能问题之一(渲染dom),当进行dom操作时,只要把dom相关操作交给vertual dom,它会进行一些处理,比如diff、patch,减少浏览器的重排与重绘。以最少的代价来渲染dom,就是最小化性能对资源池(老树)的操作

  1. 开始看源码之前再回顾了解一下,dom节点由哪些组成:

先来看下我们的HTML标签结构

<div class="demo" >
   <p class="childDemo"> Click Me</p>
</div >

DOM 元素包含的信息其实只有三个:标签名,属性,子元素。
我们用 javascript对象来表示

{
  tag: 'div',
  attrs: { className: 'demo'},
  children: [
    {
      tag: 'p',
      arrts: { className: 'childDemo' },
      children: ['Click Me']
    }
  ]
}

所有在创建DOM元素、进行深度优先遍历、diff、patch等等操作,都是围绕这几个来写源码

  1. 构建虚拟dom
//根据传进来的值 产生虚拟dom 对象
/*使用js对象来表示一个DOM节点很简单,只需要记录tagName, props, children*/
class Element{
	constructor(tagName, props, children){
		this.tagName = tagName;
		this.props = props;
		this.children = children;
	}
}
//生成虚拟dom
function createElement(tagName,props,children){
	return new Element(tagName,props,children)
}
 
let vertualDom = createElement('ul',{class:"list"},[
	createElement('li',{class:"item"},['a']),
	createElement('li',{class:"item"},['a']),
	createElement('li',{class:"item"},['a'])
])
 
//负责将虚拟dom处理成真实的dom
function render(eleObj){
	let el  = document.createElement(eleObj.tagName);
	for(let key in eleObj.props){
		//设置属性的方法
		setAttr(el,key,eleObj.props[key]);
	}
	eleObj.children.forEach((child)=>{
		child = (child instanceof Element)?render(child):document.createTextNode(child);
		el.appendChild(child)
	})
	return el;
}
 
//设置属性
function setAttr(node,key,value){
	switch(key){
		case"value":
			if(node.tagName.toUpperCase()==="INPUT"||
				node.tagName.toUpperCase()==="TEXTAREA"){
				node.value = value
			}else{
				node.setAttribute(key,value);
			}
			break;
		case"style":
			node.style.cssText = value;
		   break;
		default:
			node.setAttribute(key,value);
		   break;
 
	}
}
//把dom挂在到页面中
function renderDOm(el,target){
	target.appendChild(el);
}

简陋版虚拟dom构建完成,下面来看下dom diff操作
其实当我们在react 中setState操作时,就会重新创建一个大的对象,这个大的对象就是虚拟dom对象 ,创建完成后会进行新老vertual dom树的比较(diff),将两棵树中不同的部分抽取出来,这就是patch,将patch更新到真实dom上,然后渲染到页面。这就是以最小的代价来操作更新dom

// 对比前后两棵树 之前的差别 返回一个补丁对象
function diff(oldTree,newTree){

	let patches = {}; //补丁包
	let index = 0; //当前的节点的标志。因为在深度优先遍历的过程中,每个节点都有一个index。
	walk(oldTree,newTree,index,patches); // 开始进行深度优先遍历
	return patches; //最终返回两棵树的差异
}
//对比属性的不同 props
function diffAttr(oldAttrs,newAttrs){
	let patch = {} //差异化props
	//更新
	for(let attr in oldAttrs){
		if(oldAttrs[attr]!== newAttrs[attr]){
			patch[attr] = newAttrs[attr]
		}
	}
	//新增
	for(let attr in newAttrs){
		if(!oldAttrs.hasOwnProperty(attr)){
			patch[attr] = newAttrs[attr]
		}
	}
	return patch;
}
const ATTRS = "ATTRS";
const TEXT = "TEXT";
const REMOVE = "REMOVE";
const REPLACE = 'REPLACE';
let Index = 0; //针对children 深度优化遍历 当前的节点的标志

//递归遍历子节点的不同
function diffChildren(oldNode,newNode,index,patches){
	oldNode.forEach((child,idx)=>{
		walk(child ,newNode[idx],++Index,patches)
	})
}
function isString(node){
	return Object.prototype.toString.call(node)=="[object String]";
}
// 具体的对比方法   对两棵树进行深度优先遍历。
function walk(oldNode,newNode,index,patches){
	let currentPatches = [];

	if(!newNode){ //没有新的节点。删除
		currentPatches.push({type:REMOVE,index});
	}
	else if(isString(oldNode)&&isString(newNode)){ //判断文本是否变换
		if(oldNode!==newNode){
			currentPatches.push({type:TEXT,text:newNode});
		}
	}
	else if(oldNode.tagName === newNode.tagName){ //tagName 相同 判断属性
		let attrs = diffAttr(oldNode.props,newNode.props); //patch's props
		//将patch添加到此轮比较的补丁包里
		if(Object.keys(attrs).length>0){
			currentPatches.push({type:ATTRS,attrs})
		}
		//比较children
		diffChildren(oldNode.children,newNode.children,index,patches)
	}else{
		//节点被替换了
		currentPatches.push({type:REPLACE,newNode})
	}
	if(currentPatches.length>0){  //产生补丁包
		patches[index] = currentPatches;
	}
  • 虚拟dom创建是先序深度优化
  • diff只会对同层进行比较,参考diff三大策略
  • patch 时是倒序的,从下到上

具体的diff算法详解请查看https://blog.csdn.net/lulu_678/article/details/88249461

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值