ReactDOM.createPortal

ReactDOM.createPortal,第1张

createPortal 的调用方式是:

第一个参数是一个 renderable React child

第二个参数是一个DOM元素

将index.html页面添加DOM节点来验证createPortal如何渲染

大白话的意思是:

通过createPortal渲染的元素会被添加到另外的节点,同时点击事件会被触发;

而通过ReactDOM.render渲染的元素添加到新节点,但是点击事件没有触发。

react 是一个数据驱动的框架,通过将数据与 UI 关联起来达到数据更新时同时更新 UI 更新的目的。对于 react web app 来说,数据的变动最终会转化为 dom 的变化。当然 react 并不会对 dom 进行直接比较,而是对比变化前的 fiber。对 fiber 的 diff 最终会反映到 dom 上。

先假设在 fiber 变化时不使用 diff 算法,即一旦 fiber 改变则删除变化前的所有 fiber 并插入变化后的 fiber 。这种方法虽然简便,但存在性能问题,因为 dom 的删除和创建都需要耗费时间。例如,fiber 从 a, b, c 变为 a, c, b。只需要将 b 插入到 c 之后即可,无需创建任何 fiber 。因此,需要一种方法来标记元素的变更,这就是 diff 算法。

如果变化后都存在多个元素,则属于多节点的 diff。多节点的 fiber diff 对于每一个 fiber 实际只存在两种情况:

为什么移动或新增 dom 都属于同一种情况,因为 react 实际上最终会调用 Node.insertBefore() 来进行 placement *** 作,其定义如下:

因此 react 并不关心该 fiber 是移动(已经存在)还是新增(不存在需要创建)。例如 fiber 从 a, b, c, d 变为 a, c, b,d,那么 react 会将 b 这个 fiber 标记为 Placement。其余 fiber 不变。在最终进行 dom 变化时调用 parent.insertBefore(d, b) 。因此 diff 的目的并不是要 严格的找出 fiber 从哪个位置移动到哪个位置,只需要得出哪些需要删除,哪些需要 Placement 即可。

假设存在 now 以及 before 两个 fiber 集合。为了简化场景,认为 now 中的 fiber 在 before 中都存在。这时候问题可以转换为 如何移动 before 中的元素将其转换为 now 。react处理办法为 右移 before 中的部分 fiber 将其转换为 now 。例如,before 以及 after 中 key 的顺序为:

那么标记 b 为 Placement 即可。对于这个任务,我们将 上一个位置不变的元素在 now 中的位置记为 lastKeepIndex ,当遍历 now 数组中的每个 fiber 时,如果该 fiber 在 before 数组中存在,且。则说明当前所遍历到得 fiber 在:

这就意味这这个 fiber 是需要移动的。如果不满足这个条件,则需要该 fiber 相对 lastKeepIndex 所标记的 fiber 位置没有变动,无需改变。

当然,实际上不可能 now 中的 fiber 在 before 中都能找到。但这种同样直接标记为 Placement 即可。同时在 before 中却不在 now 中的需要元素标记为 Deletion。为了方便这里我们定义 4 种类型的 Diff:

整个 diff 的逻辑为:

在得到 diff 的结果后,react 通过两个 dom *** 作函数来将 diff 应用到真实的 dom:

第一个函数对应于变化后需要进行 Placement 有兄弟节点的情况,例如 fiber 从 a,b,c,d 变化为 a,c,b,d。此时 b 被标记为 Placement。react 会找到变化后它的第一个不需要变动的兄弟节点即为 d,并调用 parent.insertBefore(d, b) 。完成后真实的 dom 就从 a,b,c,d 变成 a,c,b,d。

第二个函数对应于变化后需要进行 Placement 不存在兄弟节点的情况,例如 fiber 从 a,b,c 变化为 a,c,b 此时 b 被标记为 Placement,但其不存在兄弟节点。react 会调用 parent.appendChild(b) 。完成后真实的 dom 就从 a,b,c 变成 a,c,b。

当然,真实的情况比这要更复杂。因此插入 dom 必定要先找到 fiber 树中真正的 dom 节点。而 fiber 树实际上是用户自定义组件 fiber 以及真实 dom fiber 组合在一起的,如何找到真实的兄弟 dom 节点对应的 fiber 也是一个比较复杂的任务。

react 通过 diff 算法来进行性能优化,减少 dom 的创建和删除。那么 react 采用的优化是否为 最优化 呢?答案是:否。例如存在这样一个特殊的例子:

由于 react diff 算法的局限,这里需要将 1 从 998 移动到 999 之后,但实际上我们一眼就能看出最简单的方法是将 999 移动到 1 之前。这也就是最近很多框架开始使用 最长上升子序列 来优化 diff 算法的原因。那么问题来了,你知道为什么这里 react 需要移动 998 个元素,或者说为什么最长上升子序列可以解决整个问题吗?

1、创建元素节点创建元素节点并且把节点作为元素的子节点添加到DOM节点树上。先创建元素点,创建元素节点使用Jquery的工厂函数$()来完成,格式如下:$(html),该方法会根据传入的html字符串返回一个DOM对象,并将DOM对象包装成一个JQuery对象后返回。创建一个元素节点JQuery代码如下:$li1=$("")代码返回$li1就是一个由DOM对象包装成的JQuery对象。把新建节点添加到DOM树中JQuery代码如下:$("ul").append($li1)添加后页面中只能看到元素默认的"·",由于没有为节点添加文本所以只显示默认符号,下面创建文本节点。PS:append()方法是添加DOM节点方法详见增--添加DOM节点。2、创建文本节点使用JQuery的工厂函数$()同样能够创建文本节点,创建文本节点的JQuery代码如下:$li2=$("苹果")代码返回$li2就是一个由DOM对象包装成JQuery对象,把新建的文本节点添加到DOM树中JQuery代码如下:$("ul").append($li2)添加后页面中能看到"·苹果",右键查看页面源码发现新加的文本节点没有title属性。下面方法创建带属性的节点。3、创建属性节点创建属性节点同元素节点、文本节点一样使用JQuery的工厂函数完成。创建属性节点的JQuery代码如下:$li3=$("榴莲")代码返回$li3也是一个由DOM对象包装成JQuery对象,把新建的属性节点添加到DOM树中JQuery代码如下:$("ul").append($li3)添加后页面中能看到"·榴莲",右键查看页面源码发现新加的属性节点有title='榴莲'属性。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/bake/11459699.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-16
下一篇2023-05-16

发表评论

登录后才能评论

评论列表(0条)

    保存