深入理解、React
虚拟DOM树渲染
虚拟dom这篇文章简单明了的介绍了虚拟DOM,结合Diff算法,这个主题进行整理。
生成Virtual DOM树
虚拟DOM是一个对象,由名称、属性、子节点、key等属性组成,子节点也是虚拟DOM对象,这样就组成了树结构:
{
// tag的名字
tagName: 'p',
// 节点包含属性
properties: {
style: {
color: '#fff'
}
},
// 子节点
children: [],
// 该节点的唯一表示,后面会讲有啥用
key: 1
}
这种对象React中是这样构建的:
// 创建一个div
react.createElement('div', null, [
// 子节点img
react.createElement('img', { src: "avatar.png", class: "profile" }),
// 子节点h3
react.createElement('h3', null, [[user.firstName, user.lastName].join(' ')])
]);
由Virtual DOM树生成DOM树
这样根据虚拟DOM树,通过调用document.createElement()就可以生成真实的DOM树,简单实现如下:
function create(vds, parent) {
// 首先看看是不是数组,如果不是数组统一成数组
!Array.isArray(vds) && (vds = [vds]);
// 如果没有父元素则创建个fragment来当父元素
parent = parent || document.createDocumentFragment();
var node;
// 遍历所有VNode
vds.forEach(function (vd) {
// 如果VNode是文字节点
if (isText(vd)) {
// 创建文字节点
node = document.createTextNode(vd.text);
} else {
// 否则创建元素
node = document.createElement(vd.tag);
}
// 将元素塞入父容器
parent.appendChild(node);
// 看看有没有子VNode,有孩子则处理孩子VNode
vd.children && vd.children.length &&
create(vd.children, node);
// 看看有没有属性,有则处理属性
vd.properties &&
setProps({ style: {} }, vd.properties, node);
});
return parent;
}
两个Virtual DOM树的Diff算法
这里做两个树的Diff算法,简单说就是逐级同层比较、并使用key来做优化。
-
逐级同层比较
如图:

这种比较大大降低了复杂度。
-
key做优化

React会将状态变化前后的组件列表中的每一个组件做一一关联,把状态变化之前的列表作为表1,之后的作为表2。表1的第一个组件关联到表2的第一个组件,依此类推。你可以提供key
属性来帮助React找出对应关系。这样就能很快找到修改(新增、替换、删除)的组件,并进行重新渲染。
ps:这里就涉及到为什么list中index不适合做component的key值。
事件机制
DOM事件
事件机制:冒泡、捕获、委托
DOM事件流(event flow )存在三个阶段:**事件捕获阶段、处于目标阶段、事件冒泡阶段。**s
事件捕获(event capturing):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。
**事件冒泡(dubbed bubbling):**与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点。
无论是事件捕获还是事件冒泡,它们都有一个共同的行为,就是事件传播,它就像一跟引线,只有通过引线才能将绑在引线上的鞭炮(事件监听器)引爆。
示例如下:
<body>
<div id="parent">
父元素
<div id="child">
子元素
</div>
</div>
<script type="text/javascript">
var parent = document.getElementById("parent");
var child = document.getElementById("child");
document.body.addEventListener("click",function(e){
console.log("click-body");
},false);
parent.addEventListener("click",function(e){
console.log("click-parent");
},false);
child.addEventListener("click",function(e){
console.log("click-child");
},false);
</script>
</body>
addEventListener(evt、listener、useCapture)
,前2个参数一个是事件,另外一个是监听函数,最后一个参数为true代表采用捕获方式,为false代表采用冒泡方式,默认为false。
事件委托 : 简单说从父元素的监听函数中,可以响应子元素的事件,子元素的事件处理委托给了父元素。
var parent = document.getElementById("parent");
var child = document.getElementById("child");
parent.onclick = function(e){
if(e.target.id == "child"){
console.log("您点击了child元素")
}
}
这样点击child时,虽然它本身没有监听,但从点击事件的target中,可以返回目标,进而进行响应。
React事件机制
react事件机制 这篇文章介绍的很详细,这里只写一下大致流程。
react 的所有事件并没有绑定到具体的dom节点上而是绑定在了document 上,然后由统一的事件处理程序来处理,同时也是基于浏览器的事件机制(冒泡),所有节点的事件都会在 document 上触发。
这里有事件注册机制、事件执行机制2种机制。
事件注册机制:
react 事件注册过程其实主要做了2件事:事件注册、事件存储。
a. 事件注册 - 组件挂载阶段,根据组件内的声明的事件类型-onclick,onchange 等,给 document 上添加事件 -addEventListener,并指定统一的事件处理程序 dispatchEvent。
b. 事件存储 - 就是把 react 组件内的所有事件统一的存放到一个对象里,缓存起来,为了在触发事件的时候可以查找到对应的方法去执行
事件执行机制:
在事件注册阶段,最终所有的事件和事件类型都会保存到 listenerBank
中。
那么在事件触发的过程中上面这个对象有什么用处呢?其实就是用来查找事件回调
大致流程:
事件触发过程总结为主要下面几个步骤:
1.进入统一的事件分发函数(dispatchEvent)
2.结合原生事件找到当前节点对应的ReactDOMComponent对象
3.开始 事件的合成
3.1 根据当前事件类型生成指定的合成对象
3.2 封装原生事件和冒泡机制
3.3 查找当前元素以及他所有父级
3.4 在 listenerBank
查找事件回调并合成到 event
(合成事件结束)
4.批量处理合成事件内的回调事件(事件触发完成 end)