返回顶部
首页 > 资讯 > 精选 >React DOM-diff节点源码分析
  • 772
分享到

React DOM-diff节点源码分析

2023-07-05 06:07:49 772人浏览 薄情痞子
摘要

本篇内容介绍了“React DOM-diff节点源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!单节点单节点的dom-dif

本篇内容介绍了“React DOM-diff节点源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

单节点

单节点的dom-diff是在reconcileSingleElement中进行的,而能否复用的判断依据就是将要更新的虚拟DOM的keyhtml元素的类型(即divp的区别)是否和当前(页面上正在渲染的)真实DOM的fiber一致。

React DOM-diff节点源码分析

如图所示,对于单节点的diff我们按照图中的流程,结合源码进行一一解读

  function reconcileSingleElement(returnFiber, currentFirstChild, element) {    //新的虚拟DOM的key,也就是唯一标准    const key = element.key;        // null    let child = currentFirstChild; //老的FunctionComponent对应的fiber    while (child !== null) {      //判断此老fiber对应的key和新的虚拟DOM对象的key是否一样 null===null      if (child.key === key) {        //判断老fiber对应的类型和新虚拟DOM元素对应的类型是否相同        if (child.type === element.type) {// p div          deleteRemaininGChildren(returnFiber, child.sibling);          //如果key一样,类型也一样,则认为此节点可以复用          const existing = useFiber(child, element.props);          existing.ref = element.ref;          existing.return = returnFiber;          return existing;        } else {          //如果找到一key一样老fiber,但是类型不一样,不能此老fiber,把剩下的全部删除          deleteRemainingChildren(returnFiber, child);        }      } else {        deleteChild(returnFiber, child);      }      child = child.sibling;    }    //因为我们现实的初次挂载,老节点currentFirstChild肯定是没有的,所以可以直接根据虚拟DOM创建新的Fiber节点    const created = createFiberFromElement(element);    created.ref = element.ref;    created.return = returnFiber;    return created;  }

key相同,类型相同

<div>  <div key='A'>A</div>   <div key='B'>B</div></div><!-- 变化到 --><div>  <div key='A'>C</div></div>

对于上面列举到的情况,新的虚拟DOM匹配到第一个即为相同key和type,我们首先通过deleteRemainingChildren方法删除掉其它的多余的子节点(上面的 <div key='B'>B</div>),然后通过useFiber方法来复用老fiber产生新的fiber,这样就完成我们的复用。

key不同,类型相同

<div>  <div key='A'>A</div>   <div key='B'>B</div></div><!-- 变化到 --><div>  <div key='C'>C</div></div>

对于上面列举到的情况,新的虚拟DOM匹配到第一个即为不同key即使type相同也不会往下进行,通过deleteChild方法删掉第一个子节点,即<div key='A'>A</div>对应的fiber,然后再对第二个子节点<div key='B'>B</div>进行对比,发现key依然不同,继续删除,删除完成之后child === null成立,跳出while循环,通过createFiberFromElement方法根据新的虚拟DOM创建新的fiber。

key相同,类型不同

<div>  <div key='A'>A</div>   <div key='B'>B</div></div><!-- 变化到 --><div>  <p key='A'>C</p></div>

对于上面列举的情况,第一次匹配到了相同的key但是type不同,依旧是不符合复用的条件,而且此时会通过deleteRemainingChildren方法删除掉所有子节点,即不会再进行第二次比较,直接就跳出循环,通过createFiberFromElement方法根据新的虚拟DOM创建新的fiber。

多节点

多节点的diff相对于单节点的diff来说更加复杂一些。这里主要是在方法reconcileChildrenArray中进行,这个过程最多会经历三次遍历,每次完成相应的功能,下面我们结合源码来具体探究一下。

  function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {    let resultingFirstChild = null; //返回的第一个新儿子    let previousNewFiber = null; //上一个的一个新的儿fiber    let newIdx = 0;//用来遍历新的虚拟DOM的索引    let oldFiber = currentFirstChild;//第一个老fiber    let nextOldFiber = null;//下一个第fiber    let lastPlacedIndex = 0;//上一个不需要移动的老节点的索引    // 开始第一轮循环 如果老fiber有值,新的虚拟DOM也有值    for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {      //先暂下一个老fiber      nextOldFiber = oldFiber.sibling;      //试图更新或者试图复用老的fiber      const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);      if (newFiber === null) {        break;      }      //如果有老fiber,但是新的fiber并没有成功复用老fiber和老的真实DOM,那就删除老fiber,在提交阶段会删除真实DOM      if (oldFiber && newFiber.alternate === null) {        deleteChild(returnFiber, oldFiber);      }      //指定新fiber的位置      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);      if (previousNewFiber === null) {        resultingFirstChild = newFiber;//li(A).sibling=p(B).sibling=>li(C)      } else {        previousNewFiber.sibling = newFiber;      }      previousNewFiber = newFiber;      oldFiber = nextOldFiber    }    //新的虚拟DOM已经循环完毕    if (newIdx === newChildren.length) {      //删除剩下的老fiber      deleteRemainingChildren(returnFiber, oldFiber);      return resultingFirstChild;    }    if (oldFiber === null) {      //如果老的 fiber已经没有了, 新的虚拟DOM还有,进入插入新节点的逻辑      for (; newIdx < newChildren.length; newIdx++) {        const newFiber = createChild(returnFiber, newChildren[newIdx]);        if (newFiber === null) continue;        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);        //如果previousNewFiber为null,说明这是第一个fiber        if (previousNewFiber === null) {          resultingFirstChild = newFiber; //这个newFiber就是大儿子        } else {          //否则说明不是大儿子,就把这个newFiber添加上一个子节点后面          previousNewFiber.sibling = newFiber;        }        //让newFiber成为最后一个或者说上一个子fiber        previousNewFiber = newFiber;      }    }    // 开始处理移动的情况    const existingChildren = mapRemainingChildren(returnFiber, oldFiber);    //开始遍历剩下的虚拟DOM子节点    for (; newIdx < newChildren.length; newIdx++) {      const newFiber = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx]);      if (newFiber !== null) {      //如果要跟踪副作用,并且有老fiber       if (newFiber.alternate !== null) {         existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key);       }        //指定新的fiber存放位置 ,并且给lastPlacedIndex赋值        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);        if (previousNewFiber === null) {          resultingFirstChild = newFiber; //这个newFiber就是大儿子        } else {          //否则说明不是大儿子,就把这个newFiber添加上一个子节点后面          previousNewFiber.sibling = newFiber;        }        //让newFiber成为最后一个或者说上一个子fiber        previousNewFiber = newFiber;      }    }    //等全部处理完后,删除map中所有剩下的老fiber    existingChildren.forEach(child => deleteChild(returnFiber, child));    return resultingFirstChild;  }

这段代码是比较长的,这里全部贴出来就是体现其完整性。下面帮助大家逐步的分析。

<ul key="container">  <li key="A">A</li>  <li key="B">B</li>  <li key="C">C</li>  <li key="D">D</li>  <li key="E">E</li>  <li key="F">F</li></ul><!-- 变化到 --><ul key="container">  <li key="A">A2</li>  <li key="C">C2</li>  <li key="E">E2</li>  <li key="B">B2</li>  <li key="G">G</li>  <li key="D">D2</li></ul>

第一次遍历

for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {      //先暂下一个老fiber      nextOldFiber = oldFiber.sibling;      //试图更新或者试图复用老的fiber      const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);      if (newFiber === null) {        break;      }      if (shouldTrackSideEffects) {        //如果有老fiber,但是新的fiber并没有成功复用老fiber和老的真实DOM,那就删除老fiber,在提交阶段会删除真实DOM        if (oldFiber && newFiber.alternate === null) {          deleteChild(returnFiber, oldFiber);        }      }      //指定新fiber的位置      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);      if (previousNewFiber === null) {        resultingFirstChild = newFiber;//li(A).sibling=p(B).sibling=>li(C)      } else {        previousNewFiber.sibling = newFiber;      }      previousNewFiber = newFiber;      oldFiber = nextOldFiber    }

我们所有的对比都是基于新节点的虚拟DOM和老节点的fiber,当我们对比A1和A2时,会根据updateSlot方法进行条件判断,发现他们的key和type相同,符合复用条件返回创建好的fiber,我们的操作指针都指向下一个操作节点,开始对下一个节点进行第一次遍历。

当我们对比C2和B时,因为C2和B的key并不相同,updateSlot返回null,第一次遍历break开始进入第二次遍历。

第二次遍历

if (oldFiber === null) {  //如果老的 fiber已经没有了, 新的虚拟DOM还有,进入插入新节点的逻辑  for (; newIdx < newChildren.length; newIdx++) {    const newFiber = createChild(returnFiber, newChildren[newIdx]);    if (newFiber === null) continue;    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);    //如果previousNewFiber为null,说明这是第一个fiber    if (previousNewFiber === null) {      resultingFirstChild = newFiber; //这个newFiber就是大儿子    } else {      //否则说明不是大儿子,就把这个newFiber添加上一个子节点后面      previousNewFiber.sibling = newFiber;    }    //让newFiber成为最后一个或者说上一个子fiber    previousNewFiber = newFiber;  }}

然而oldFiber依旧是存在的,会直接进入到第三次遍历,但是我们这里带大家梳理一下,看看是如何操作的。这里的遍历主要是针对新节点还存在,但是老fiber已经没有了,即新更新的节点要多余老节点的情况,我们这里需要做的就是将剩下的新节点的fiber通过createChild创造出来。

第三次遍历

// 开始处理移动的情况const existingChildren = mapRemainingChildren(returnFiber, oldFiber);//开始遍历剩下的虚拟DOM子节点for (; newIdx < newChildren.length; newIdx++) {  const newFiber = updateFromMap(    existingChildren,    returnFiber,    newIdx,    newChildren[newIdx],  );  if (newFiber !== null) {  //如果要跟踪副作用,并且有老fiber  if (newFiber.alternate !== null) {    existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key);  }    //指定新的fiber存放位置 ,并且给lastPlacedIndex赋值    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);    if (previousNewFiber === null) {      resultingFirstChild = newFiber; //这个newFiber就是大儿子    } else {      //否则说明不是大儿子,就把这个newFiber添加上一个子节点后面      previousNewFiber.sibling = newFiber;    }    //让newFiber成为最后一个或者说上一个子fiber    previousNewFiber = newFiber;  }}function mapRemainingChildren(returnFiber, currentFirstChild) {  const existingChildren = new Map();  let existingChild = currentFirstChild;  while (existingChild != null) {    //如果有key用key,如果没有key使用索引    if (existingChild.key !== null) {      existingChildren.set(existingChild.key, existingChild);    } else {      existingChildren.set(existingChild.index, existingChild);    }    existingChild = existingChild.sibling;  }  return existingChildren;}

接下来我们进行第三次遍历,也就是我们节点移动的情况,这里的复用是比较复杂了。

首先我们会创造一个Map来承接所有的剩余的老节点,接下来我们会根据key,或者index,来挑选老节点以供复用。找到一个能复用的节点,就会在Map中删除对应的节点,如果有对应的点就复用,没有就新创建节点。

React DOM-diff节点源码分析

  • 多个节点数量不同、key 不同;

  • 第一轮比较 A 和 A,相同可以复用,更新,然后比较 B 和 C,key 不同直接跳出第一个循环;

  • 把剩下 oldFiber 的放入 existingChildren 这个 map 中;

  • 然后声明一个lastPlacedIndex变量,表示不需要移动的老节点的索引;

  • 继续循环剩下的虚拟 DOM 节点;

  • 如果能在 map 中找到相同 key 相同 type 的节点则可以复用老 fiber,并把此老 fiber 从 map 中删除;

  • 如果能在 map 中找不到相同 key 相同 type 的节点则创建新的 fiber;

  • 如果是复用老的 fiber,则判断老 fiber 的索引是否小于 lastPlacedIndex,如果是要移动老 fiber,不变;

  • 如果是复用老的 fiber,则判断老 fiber 的索引是否小于 lastPlacedIndex,如果否则更新 lastPlacedIndex 为老 fiber 的 index;

  • 把所有的 map 中剩下的 fiber 全部标记为删除;

  • (删除#li#F)=>(添加#li#B)=>(添加#li#G)=>(添加#li#D)=>null。

“React DOM-diff节点源码分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

--结束END--

本文标题: React DOM-diff节点源码分析

本文链接: https://lsjlt.com/news/350158.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • React DOM-diff节点源码分析
    本篇内容介绍了“React DOM-diff节点源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!单节点单节点的dom-dif...
    99+
    2023-07-05
  • ReactDOM-diff节点源码解析
    目录前言单节点key相同,类型相同key不同,类型相同key相同,类型不同多节点第一次遍历第二次遍历第三次遍历总结前言 这篇文章帮助大家梳理一下React中的dom-diff。在R...
    99+
    2023-02-27
    React DOM-diff React DOM-diff 节点
  • react diff算法源码解析
    目录单节点Diff reconcileSingleElement多节点Diff reconcileChildrenArray如何判断节点移动了? 总结 React中Diff算法又称为...
    99+
    2024-04-02
  • JQuery中DOM节点的示例分析
    小编给大家分享一下JQuery中DOM节点的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!具体如下:Jquery中DOM...
    99+
    2024-04-02
  • HTML中DOM节点的示例分析
    小编给大家分享一下HTML中DOM节点的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!  在HTML DOM中,所有事物...
    99+
    2024-04-02
  • React Diff原理深入分析
    目录Diffing 算法逐层比较对比同类型的组件元素对比同一类型的元素对子节点进行递归Keys在了解Diff前,先看下React的虚拟DOM的结构 这是html结构 <di...
    99+
    2024-04-02
  • React深入分析useEffect源码
    目录热身准备初始化 mount更新 updateupdateEffect执行副作用总结热身准备 这里不再讲useLayoutEffect,它和useEffect的代码是一样的,区别主...
    99+
    2022-11-13
    React useEffect React useEffect源码
  • Vue源码分析之虚拟DOM的示例分析
    小编给大家分享一下Vue源码分析之虚拟DOM的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!为什么需要虚拟dom?虚拟DOM就是为了解决浏览器性能问题而被...
    99+
    2023-06-15
  • ElasticSearch节点、分片、CRUD、倒排索引和分词源码分析
    这篇文章主要介绍了ElasticSearch节点、分片、CRUD、倒排索引和分词源码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇ElasticSearch节点、分片、CRUD、倒排索引和分词源码分析文章都...
    99+
    2023-07-05
  • Vue源码分析之虚拟DOM详解
    为什么需要虚拟dom? 虚拟DOM就是为了解决浏览器性能问题而被设计出来的。例如,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内...
    99+
    2024-04-02
  • react-router-dom路由入门实例代码分析
    这篇文章主要介绍了react-router-dom路由入门实例代码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇react-router-dom路由入门实例代码分析文章都会有所收获,下面我们一起来看看吧。r...
    99+
    2023-07-05
  • Vue3源码分析组件挂载创建虚拟节点
    目录前情提要1. Mount函数2. 创建虚拟节点的几个方法(1) createVNode:用于创建组件的虚拟节点(2) createElementVNode:用于创建普通tag的虚...
    99+
    2022-11-13
    Vue3组件挂载创建虚拟节点 Vue3组件挂载
  • React中的Virtual DOM示例分析
    本篇内容主要讲解“React中的Virtual DOM示例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“React中的Virtual DOM示例分析”吧!这是Choero...
    99+
    2023-06-29
  • React中setState源码的示例分析
    这篇文章将为大家详细讲解有关React中setState源码的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。React作为一门前端框架,虽然只是focus在MVV...
    99+
    2024-04-02
  • React深入浅出分析Hooks源码
    目录useState 解析useState 使用useState 模拟实现hooks 规则useEffect 解析useEffect 使用useEffect 的模拟实现useEffe...
    99+
    2022-11-13
    React Hooks React Hooks源码
  • Vue2.x中虚拟DOM diff原理的示例分析
    这篇文章主要介绍Vue2.x中虚拟DOM diff原理的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!前言经常看到讲解Vue2的虚拟Dom diff原理的,但很多都是在原代码...
    99+
    2024-04-02
  • Vue中的虚拟DOM和Diff算法实例分析
    这篇文章主要介绍了Vue中的虚拟DOM和Diff算法实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue中的虚拟DOM和Diff算法实例分析文章都会有所收获,下面我们一起来看看吧。简单介绍一下 虚拟DO...
    99+
    2023-06-29
  • Javascript中DOM、节点和获取元素的示例分析
    这篇文章给大家分享的是有关Javascript中DOM、节点和获取元素的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。DOM文档:DOM中的“D”,当创建一个网页并把它加载到Web浏览器中时,它把编写的网...
    99+
    2023-06-25
  • 如何使用React和DOM的节点删除算法
    本篇内容主要讲解“如何使用React和DOM的节点删除算法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何使用React和DOM的节点删除算法”吧!Fiber...
    99+
    2024-04-02
  • vue原理Compile之optimize标记静态节点源码分析
    这篇文章主要介绍“vue原理Compile之optimize标记静态节点源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“vue原理Compile之optimize标记静态节点源码分析”文章能帮...
    99+
    2023-07-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作