React源码解析18(10)------ 实现多节点的Diff算法

摘要

在上一篇中,实现了多节点的渲染。但是之前写得diff算法,只能适用于单节点的情况,例如这种情况:

javascript 复制代码
<div>
	<p>
 		<span></span>
	</p>
</div>

如果对于多节点的情况:

javascript 复制代码
<ul>
	<li></li>
	<li></li>
	<li></li>
</ul>

之前实现的diff算法就不会有效果了,所以在这一篇中,我们主要实现针对于多节点的diff算法。

实现之前,我们先将index.js修改一下:

javascript 复制代码
function App() {
  const [num, setNum] = useState(100)
  const click1 = () => {
    console.log(num);
    setNum(num + 1)
  }
  return num % 2 > 0 ? jsx("ul", {
    onClick: click1,
    key: 'ul',
    children: [jsx("li", {
      children: "1",
      key: "1"
    }), jsx("li", {
      children: "2",
      key: "2"
    }), jsx("li", {
      children: "3",
      key: "3"
    })]
  }): jsx("ul", {
    onClick: click1,
    key: 'ul',
    children: [jsx("li", {
      children: "2",
      key: "2"
    }), jsx("li", {
      children: "1",
      key: "1"
    }), jsx("li", {
      children: "3",
      key: "3"
    })]
  });
}

ReactDOM.createRoot(root).render(<App />)

1.修改beginWork流程

在reconcileChildren方法里面,我们判断了如果element为数组的情况,就是多节点。所以我们需要在这里进行diff算法的处理。

javascript 复制代码
function reconcileChildren(parent,element) {
  //其他代码。。。。
  }else if(Array.isArray(element) && element.length > 0) {
    const newChild = diffReconcileManyChildren(parent, element);
    if(newChild) {
      return newChild
    }
  //其他代码。。。。

所以我们的diff算法那主要是在diffReconcileManyChildren方法里面实现。

对于多节点的Diff,我们需要进行以下步骤。

  1. 创建变量lastIndex,用来标记索引
  2. 将旧的filberNode列表,转换为map结构,key为filberNode的key,value为filberNode
  3. 遍历新的element数组。
  4. 如果element.key可以在map中找到,lastIndex记录为找到的filberNode的index
  5. 如果找不到,创建新的FilberNode
  6. 继续遍历,如果又在map中找到filberNode,比较fiberNode的index和lastIndex.
  7. 如果index < lastIndex,给filberNode打上移动的标志

基于上面的步骤,实现diffReconcileManyChildren方法

javascript 复制代码
function diffReconcileManyChildren(filberNode, element) {
  let firstChild = filberNode.child;
  if(!firstChild) {
    return;
  }
  const head = {
    sibling: null
  };
  const oldChildren = []
  while(firstChild) {
    oldChildren.push(firstChild);
    firstChild = firstChild.sibling;
  }
  const oldMap = new Map();
  oldChildren.forEach((item,index) => {
    item.index = index
    if(item.key) {
      oldMap.set(item.key, item)
    }else{
      oldMap.set(index, item)
    }
  })
  let lastIndex = 0;
  let empty = head
  for(let i=0; i<element.length; i++) {
    if(!element[i].key){
      continue;
    }
    const useFilber = oldMap.get(element[i].key);
    useFilber.sibling = null;
    if(useFilber) {
      if(useFilber.index < lastIndex) {
        useFilber.flags = 'insert'
      }
      useFilber.memoizedProps = element[i]
      lastIndex = useFilber.index;
      empty.sibling = useFilber;
      empty = empty.sibling;
      oldMap.delete(element[i].key)
    }else{
      const filberNode = new FilberNode(HostComponent, element[i].props, element[i].key) 
      filberNode.type = element[i].type
      empty.sibling = filberNode;
      empty = empty.sibling;
    }

  }
  return head.sibling;
}

经过上面的处理,beginWork流程结束,可复用的filberNode就不会重复创建。

2.修改completeWork流程

在beginWork中,可复用的节点已经被打上了insert的标志,所以在updateCompleteHsotComponent中,我们要判断是不是insert的标志,如果是,就不能无脑创建,而是通过移动DOM的位置来复用DOM。

同时,也要对同级的sibling进行递归处理。

javascript 复制代码
function updateCompleteHostComponent(filberNode) {
  //其他代码。。。。
  if(element.key === filberNode.key && element.type === filberNode.type) {
    addPropsToDOM(filberNode.stateNode, filberNode.pendingProps);
    if(filberNode.flags === 'insert') {
      const parent = filberNode.return;
      parent.stateNode.insertBefore(filberNode.stateNode, filberNode.sibling?.stateNode)
    }
  //其他代码
  if(filberNode.sibling) {
    completeWork(filberNode.sibling)
  }
}

在对HostText的处理中,也要考虑,当前的操作是更新还是替换。

javascript 复制代码
function completeHostText(filberNode) {
  //其他代码。。。。。
  if(parent && parent.stateNode && parent.tag === HostComponent) {
    if(!parent.stateNode) {
      parent.stateNode.appendChild(element);
    }else{
      parent.stateNode.replaceChildren(element);
    }
  }
  //其他代码。。。。
}
相关推荐
电鱼智能的电小鱼41 分钟前
基于电鱼 AI 工控机的智慧工地视频智能分析方案——边缘端AI检测,实现无人值守下的实时安全预警
网络·人工智能·嵌入式硬件·算法·安全·音视频
孫治AllenSun1 小时前
【算法】图相关算法和递归
windows·python·算法
格图素书2 小时前
数学建模算法案例精讲500篇-【数学建模】DBSCAN聚类算法
算法·数据挖掘·聚类
DashVector3 小时前
向量检索服务 DashVector产品计费
数据库·数据仓库·人工智能·算法·向量检索
AI纪元故事会3 小时前
【计算机视觉目标检测算法对比:R-CNN、YOLO与SSD全面解析】
人工智能·算法·目标检测·计算机视觉
夏鹏今天学习了吗3 小时前
【LeetCode热题100(59/100)】分割回文串
算法·leetcode·深度优先
卡提西亚3 小时前
C++笔记-10-循环语句
c++·笔记·算法
还是码字踏实3 小时前
基础数据结构之数组的双指针技巧之对撞指针(两端向中间):三数之和(LeetCode 15 中等题)
数据结构·算法·leetcode·双指针·对撞指针
小李小李不讲道理5 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
Coovally AI模型快速验证5 小时前
当视觉语言模型接收到相互矛盾的信息时,它会相信哪个信号?
人工智能·深度学习·算法·机器学习·目标跟踪·语言模型