1.React的算算法Diff算法 - 学习笔记(21)
2.React原理剖析之diff算法,一点也不难!法源!原理!算算法
3.搞懂React源码系列-React Diff原理
4.深入浅出 虚拟DOM、法源Diff算法核心原理(源码解析)
React的原理devexpress 16.1源码Diff算法 - 学习笔记(21)
在渲染阶段,对于需要更新的算算法组件,React 会进行 Diff 算法,法源比较当前组件与上次更新时对应的原理 Fiber 节点,生成新的算算法 Fiber 节点。这一算法的法源核心目标是高效地识别组件的变化,以最小化不必要的原理 DOM 操作,提高性能。算算法
Diff 算法的法源目标是避免对不同组件类型的子树进行匹配,推荐使用相同的原理组件类型以保持组件实例和 DOM 节点的一致性。Key 的选择应保持稳定、预测性和唯一性,以确保组件实例和 DOM 节点的稳定性,避免不必要的重建导致性能下降和状态丢失。
结合渲染与提交阶段,一个 DOM 节点最多与四个节点相关联。Diff 算法存在瓶颈,其复杂度为 O(n^3),在展示大量元素时会导致大量计算。为此,React 采取了预设限制,优化 Diff 算法的执行效率。
Diff 算法的实现主要通过 `reconcileChildFibers` 函数进行,根据 `newChild` 类型调用相应的测算付费源码处理函数。React 使用了双阶段遍历法来优化 Diff 算法的执行过程,进一步提升性能。
在单节点 Diff 中,算法会根据节点类型调用特定的处理函数,如 `reconcileSingleElement`。在多节点 Diff 中,算法会根据子节点的数量进行分类处理,分别处理节点新增、减少、位置变化等情况。
在多节点 Diff 的情况下,算法首先识别更新节点的存在与否,然后基于此执行不同的操作。在节点数量减少时,算法需要标记被删除的节点;在节点数量增加时,算法会为新增的节点创建新的 Fiber 节点。当节点位置发生改变时,算法需要更新节点的位置标记。
为了确保 Diff 算法的高效执行,React 使用了 Map 结构存储每个节点的 key 和 Fiber 节点,以便快速查找和匹配。此外,算法还利用了 lastPlaceIndex 变量来跟踪节点的位置信息,判断节点在两次更新过程中的相对位置是否发生改变。
通过上述方法,Diff 算法在 React 中实现了高效地识别和处理组件更新,从而优化了性能并减少了不必要的 DOM 操作。这种机制不仅提高了应用的搜狐 棋牌源码响应速度,还确保了状态的一致性和组件实例的稳定性。
React原理剖析之diff算法,一点也不难!!!
diff算法听起来很深奥。其实真的没那么难。今天我们就用最简单的话把diff算法讲清楚。虚拟Dom什么是虚拟Dom在传统的dom中只有真实Dom↓
这玩意就是真实DOM,我们能看见的节点元素。↓
react/vue这些组件化的框架诞生后,增加了一个概念,虚拟Dom。所以,在传统的浏览器和真实Dom之间增加了一层。虚拟Dom层。成了这样↓
虚拟DOM本质上他就只是一个对象!!!没什么难的,虚拟DOM就只是一个对象而已。只是这玩意,我们不能在视图上只管的看见,它在源码里。
我们拿一段真实DOM来映射一下与之对应的虚拟Dom↓
//真实DOM<divclass="d-class"key="d"><pclass="p-class">我是p元素</p></div>//与之对应的虚拟DOMconstvNode={ key:"d",//是否有key,有则显示,无则显示nullprops:{ //标签里是否子元素children:[{ type:'p',....},],onClick:()=>{ },//标签上的事件className:"d-class",//标签上的属性}ref:"null",type:"div"}vNode这种对象,这玩意就是react中虚拟DOM的真实面目。
为什么要有虚拟DOM因为重新从头生成个正正经经的android源码 姨妈DOM节点开销实在是太大了!!!从浏览器开始创建一个节点到这个节点完全渲染到视图上去,是比较耗费性能的,当一群节点都需要去创建并渲染的时候,极端情况下,会出现肉眼可见的卡顿。虚拟Dom的作用就是尽量减少元素的创建。
我们再来一个简单的流程解释一下
//dom结构一<divclass="d-class"><pclass="p-class">我是p元素</p><div>后面需要被改变的标签<div></div>//dom结构二<divclass="d-class"><pclass="p-class">我是p元素</p><h1>节点被更改<h1></div>观察上面的dom节点一和dom节点二,不难发现。其实只有div改变成了h1。但就是这一个小小的改动,浏览器重新渲染的话,不用虚拟dom的情况下浏览器会这么做↓
删除所有节点
重新创建div标签,给div标签赋予class
在div下重新创建p标签,给P标签增加class并填充文本
在div下重新创建h1标签并填充文本
观察三个步骤,其中最耗费性能的,就是创建节点的过程。
在使用虚拟DOM的情况下。更新视图会有如下操作↓
比对结构一和结构二的树结构
发现div标签没有变。直接拿来复用(这样就不用删除也不用重新创建了)
发现p标签也没有变动,可以直接拿来复用(不用重新删除并创建)
发现div标签变成了h1标签,删除原先div标签,重新创建h1标签并插入(只有这一个需要重新生成)
然后就明朗了。
在不使用虚拟DOM的情况下,哪怕改变的只有一个子节点,所有的节点也都需要重新渲染。(我们以前使用JQ的网页字体 源码那个时代,就是这么干的)
在使用虚拟DOM的情况下。有很多没有改变的节点(或组件)可以被复用,直接省掉了好多创建节点的步骤。所以虚拟DOM会带来性能上的提升。
diff算法很简单,在虚拟DOM中,会有两棵树,一颗修改前的树,一颗修改后的树。
在react中,diff中有三大策略。
diff三大策略Treediff?(树比对)
比较新旧两棵树,找出哪些节点需要更新
发现组件则进入Componentdiff
发现节点则进入Elementdiff
Componentdiff(组件比对)
如果节点是组件,比对组件类型
类型不同直接替换(删除旧组件,创建新组件)
类型相同则只更新属性
然后深入组件进行Treediff(递归)
Elementdiff(元素比对)
如果节点是原生标签,则看标签名
标签名不同直接替换(删除旧元素,创建新元素)
标签名相同则只更新属性
然后深入标签做Treediff(递归)
比对会从Treediff(树比对开始),扫到组件进入Componentdiff,扫到元素则进入Elemenetdiff。这是一个递归查询的过程。
key是干啥的?这玩意是如何对项目进行优化的。首先,diff会对新旧两颗树进行同级比较。绝不会出现跨层比较的情况。
在某一层比对发现元素类型不一样之后,自该层往下节点会直接全部删除,不会再往下比对。
更有意思的是↓
当组件树发生上图这样的变化时,从一个人类的思维来说,应该是删除p元素,再把span元素移动到左边。
但机器不是这么理解的!!
在机器的理解上,diff算法会对以上例子做出以下动作
比对新旧节点数第一层的div是否被更改,没有改动,进入下一层比较
发现旧节点树下第一个元素是p。新节点树下的第一个节点是span(p比span)
删除p元素,创建span元素。
再进行比对,发现旧节点树第二个元素是span,新节点树没有第二个元素(span比undefined)
删除span元素
也就是说,如果你删除第一个元素,后面所有的元素都会被删除然后重新创建。
基于机器这种笨重的思维方式情况下。
key诞生了。
说白了。key是一种标记。
没有用key的情况下机器默认的算法是按顺序一个一个去比较(注意奥,是按元素顺序去比较)。只要对应顺序上的元素类型对不上,直接删除然后重建。
但是!!用了key则不一样。用了key机器会开启另一种比较算法。
当虚拟dom发现一个节点存在key以后。他就不会按顺序去比较旧节点树中相同位置的那个元素。而是会在旧树的同级元素中遍历寻找该key所标记的节点是否存在。是否可以复用。
不使用key的情况
如上图所示,在不使用key的情况下。diff算法会根据元素的位置去比对。如果你像上图那样删除了第一个元素的话,后面的元素都会因为比对不成功而全部被删除然后重新创建。
在使用key的情况下
在使用了key的情况下,会寻找匹配新旧节点树中是否有可以复用的元素。这样可以避免不必要的性能浪费。
总结首先,虚拟dom本质上就只是一个js对象而已
diff算法有三大策略,树比对(Treediff),组件比对(Componentdiff),元素比对(Elementdiff)
尽量使用key,在增删元素的时候可以提高性能
创建并渲染一个节点的流程相对耗费性能,所以需要虚拟dom来尽量减少节点的重新创建和删除
原文:/post/搞懂React源码系列-React Diff原理
时隔2年,重新审视React源码,理解了许多过去未曾明晰的内容。本文聚焦于通过实际案例结合相关React源码,集中探讨React Diff原理。我们将使用当前最新React版本:..1。同时,今年计划推出“搞懂React源码系列”,旨在以通俗易懂的方式深入解析React的核心部分。年,该系列将涵盖以下内容:
React Diff原理
React 调度原理
搭建阅读React源码环境-支持所有版本断点调试
React Hooks原理
欢迎关注我的博客。
在深入Diff算法之前,有必要先理解React Fiber。虽然Fiber并不复杂,但全面理解需要时间。本文重点是Diff原理,因此Fiber介绍将简要进行。
Fiber是React中的抽象节点对象,它将所有节点连接成链表树。每个Fiber可能包含子Fiber、相邻Fiber以及父Fiber。React使用链表形式连接所有Fiber,形成树结构。Fiber还带有副作用标签(effectTag),如替换(Placement)和删除(Deletion),用于后续更新DOM。
值得注意的是,React Diff过程中,除了Fiber,还涉及基本的React元素对象,如将foo编译后的对象为:{ type: 'div', props: { children: 'foo' } }。
Diff过程始于reconcileChildren(...)
总流程如下:
reconcileChildren(...)
在Diff时,比较中的旧内容为Fiber,而新内容为React元素、文本或数组。新内容为对象时,流程分为两步:reconcileSingleElement(...)和placeSingleChild(...)。以React元素为例,流程如下:
reconcileSingleElement(...)
这里正式开始Diff,child为旧内容Fiber,element为新内容,它们的元素类型不同。React将“删除”旧内容Fiber及其所有相邻Fiber(添加Deletion标签),并基于新内容生成新的Fiber。然后将新Fiber设置为父Fiber的child。
如果新旧内容的元素类型相同,则通过fiber复用生成新的Fiber。同样设置为父Fiber的child。
总结新内容为React元素的Diff流程:
reconcileChildren(...)
新内容为文本时,逻辑与新内容为React元素类似。新内容为数组时,通过遍历数组元素,与React元素的处理方式类似,生成新的Fiber。
总结新内容为数组的Diff流程:
reconcileChildrenArray(...)
Diff的三种情况总结:比较结果相同:复用旧内容Fiber,结合新内容生成新Fiber
比较结果不同:仅通过新内容创建新Fiber
然后给旧内容Fiber添加替换标签,或给旧内容Fiber及其所有相邻元素添加删除标签。最后将新的(第一个)Fiber设为父Fiber的child。深入浅出 虚拟DOM、Diff算法核心原理(源码解析)
五一假期后,笔者试图通过面试找到新工作,却意外地在Diff算法的挑战中受挫。为了不再在面试中尴尬,我熬夜研究了源码,希望能为即将面临同样挑战的朋友们提供一些帮助。
首先,让我们来理解什么是虚拟DOM。真实DOM的渲染过程是怎样的?为什么需要虚拟DOM?想象一下,每次DOM节点更新,浏览器都要重新渲染整个树,这效率低下。虚拟DOM应运而生,它是一个JavaScript对象,用以描述DOM结构,包括标签、属性和子节点关系。
虚拟DOM的优点在于,通过Diff算法,它能对比新旧虚拟DOM,仅更新变动的部分,而非整个DOM,从而提升性能。Diff算法主要流程包括:对比旧新虚拟DOM的差异,确定需要更新的节点,然后仅更新这部分的真实节点。
例如在React、Vue等框架中,Vue2.x采用深度优先策略,而Vue3.x可能使用不同方法。核心的patch.js文件中,patchNode函数会处理添加、删除和更新子节点的情况,采用双端比较策略,确保高效更新。
虽然文章已在此打住,但思考题仍在:当新节点(newCh)比旧节点(oldCh)多时,如何处理多出的节点?试着模拟这个过程,通过画图理解,这将有助于深入理解Diff算法的工作原理。