导言
这篇文章主要是为了学习和记录。
讲了react的事件机制 ,diff算法 以及 16.8引入的 fiber架构
react合成事件详情解释
如果dom上绑定了过多的事件处理函数,整个页面响应以及内存可能会有影响。 react为了避免这类DOM事件的滥用,实现了一个中间层。
在document处监听所有支持的事件,当事件发生并冒泡到document上时,交给中间层。
当事件触发时,使用统一的dispatchEvent将指定函数执行
javascript
export default class Test extends Component {
constructor() {
super(arguments);
this.onReactClick.bind(this);
}
componentDidMount() {
const parentDom = ReactDOM.findDOMNode(this);
const childrenDom = parentDom.querySelector(".button");
childrenDom.addEventListener('click', this.onDomClick, false);
}
onDomClick() { // 事件委托
console.log('Javascript Dom click');
}
onReactClick() { // react合成事件
console.log('React click');
}
render() {
return (
<div>
<button className="button" onClick={this.onReactClick}>点击</button>
</div>
)
}
}
合成事件和原生事件混合使用
- 响应顺序
addEventListner
重点是addEventListner的第三个参数
- 默认是false
事件冒泡和事件捕获
HTML DOM中的事件有两种传播方式,即冒泡和捕获。事件传播是一种在事件发生时定义元素顺序的方法。
- 冒泡: 即从里到外
- 捕获:即从外到里
addEventListner的第三个参数设置为true,表示使用捕获捕获
react16.8的 diff算法
虚拟DOM
虚拟DOM的本质是一个对象。由于操作真实DOM的成本比较高,所以推出了虚拟DOM。
在页面结果需要修改时,先修改虚拟DOM的节点,然后再对比 新旧两棵DOM树的差异。
最后把差异patch到真实DOM树上。
另外,也正是 基于虚拟DOM,react实现了自己的一套事件机制,并不少在事件绑定在一个确定的节点上,而是利用事件冒泡的过程,采用事件代理,批量更新的方式,从而也磨皮了各个浏览器之间的事件兼容性问题。
传统的diff算法
在计算一棵树转为另一课树中有哪些变化,传统的diff算法通过循环递归对比节点的方式,依次对比,其时间复杂度是 O(n^3)
举个例子:
js
function diffTrees(oldTree, newTree) {
const differences = [];
function diffNodes(oldNode, newNode, path) {
if (oldNode === newNode) {
// 节点完全相同,无需处理
return;
}
if (!oldNode && newNode) {
// 新增节点
differences.push({ type: "ADDED", path, node: newNode });
return;
}
if (oldNode && !newNode) {
// 删除节点
differences.push({ type: "REMOVED", path, node: oldNode });
return;
}
// 节点内容改变
if (typeof oldNode === "object" && typeof newNode === "object") {
// 假设节点是对象,并且我们关心的是它们的属性差异
const oldKeys = Object.keys(oldNode);
const newKeys = Object.keys(newNode);
// 找出属性增加、删除或修改的情况
for (const key of oldKeys) {
if (!newNode.hasOwnProperty(key)) {
// 属性被删除
differences.push({
type: "REMOVED_ATTR",
path: [...path, key],
oldValue: oldNode[key],
});
} else {
// 递归对比属性值
diffNodes(oldNode[key], newNode[key], [...path, key]);
}
}
for (const key of newKeys) {
if (!oldNode.hasOwnProperty(key)) {
// 属性被添加
differences.push({
type: "ADDED_ATTR",
path: [...path, key],
newValue: newNode[key],
});
}
}
} else {
// 节点内容直接改变(不是对象)
differences.push({
type: "CHANGED",
path,
oldValue: oldNode,
newValue: newNode,
});
}
}
// 开始对比树的根节点
diffNodes(oldTree, newTree, []);
return differences;
}
// 示例
const oldTree = {
name: "Old Root",
children: [{ name: "Old Child 1" }, { name: "Old Child 2", age: 5 }],
};
const newTree = {
name: "Root", // 名称变化
children: [
{ name: "Old Child 1", age: 10 }, // 添加年龄属性
{ name: "Updated Child 2", age: 6 }, // 名称变化
{ name: "New Child 3" }, // 新增节点
],
};
const differences = diffTrees(oldTree, newTree);
console.log(differences);
react 的diff算法
将O(n^ 3) 转化为O(n)
react 通过三大策略完成了优化
- 同级比较
由于dom节点跨层级移动的操作比较少,在对比的过程中,如果发现节点不在了,会完全删除,然后重新创建。
- 组件比较
组件比较有两种策略
-
相同类型组件
-
不同类型组件
-
相同类型组件
组件类型没有改变,但属性改变了,react会更新该组件的属性,并调用render方法
- 不同类型组件
旧的组件会被卸载,新的组件会被挂载
- 节点比较
对于同一层级的一子自节点,通过唯一的key进行比较
为什么不推荐使用 index作为 key值呢?
例如:当用 index作为key时
当数组中间插入,或者数组重新排序后,下标对应的组件就改了,那么key也更改,基本所有的节点都必须重新渲染一遍。
使用id,唯一值才是正确的。
react fiber架构
为什么要推出 react fiber呢 ?
得先了解一下一些基本知识
进程与线程
浏览器的一个tab(标签页)是一个进程。其中包含了多个线程。例如
- GUI渲染线程:主要负责渲染浏览器页面。HTML和 CSS的解析,构建DOM树和 CSSOM树。当页面需要重绘 或者回流时,该线程就会执行。 值得注意的是,GUI渲染线程和 js执行线程是互斥的。
还有一点:
会阻塞GUI渲染线程的是javaScript的同步任务(例如递归,循环,计算密集型任务)
而使用处理异步任务时(如:promise, async/await ),会挂载到异步任务队列中, 并不会阻塞渲染。
- js执行线程。负责处理js脚本。
react 和 vue的响应式原理
在react中, 组件的状态是不能被修改的。 setState没有修改原来那块内存中的变量,而是去新开辟一块内存。vue则是直接修改保存状态的那块原始内存。
数据修改后,react中,调用setState方法后,会自顶向下重新渲染组件,自顶向下的含义是,该组件以及它的子组件全部需要渲染。
而vue是使用了Object.defineProperty对数据设置的 setter 和 getter进行劫持。 也就是说,vue能知道哪个视图模版中用到了这个数据,然后只更新这个组件即可。
这也就意味了,react的组件渲染是很消耗性能的,因为你要对比当前组件以及子组件的虚拟DOM树。
为什么要推出 react fiber呢?
react组件更新是CPU密集的操作,因为它要做对比新旧两颗虚拟DOM树的操作,找到更新的内容,patch更新到真实DOM上。由于这种操作时通过递归方式实现的,是同步且不可中断的。所以过多节点对比,会直接影响 ui渲染,影响用户体验。
fiber是什么?
react fiber并没有让 diff算法的时间缩短,而是把 diff的过程分成了一小段一小段, 称为 fiber. 由于使用了链表这种结构,可以当有高优先级的任务执行时中断,当浏览器空闲时恢复,底层使用 reqestIdCallback API来判断浏览器是否空闲,最后直到完成diff后,一次性更新到视图上。
替代了原有同步且不可中断的递归diff方式。