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);
    }
  }
  //其他代码。。。。
}
相关推荐
算法鑫探2 小时前
闰年判断:C语言实战解析
c语言·数据结构·算法·新人首发
WBluuue2 小时前
数据结构与算法:康托展开、约瑟夫环、完美洗牌
c++·算法
编程牛马姐2 小时前
独立站SEO流量增长:提高Google排名的优化方法
前端·javascript·网络
木子墨5162 小时前
LeetCode 热题 100 精讲 | 并查集篇:最长连续序列 · 岛屿数量 · 省份数量 · 冗余连接 · 等式方程的可满足性
数据结构·c++·算法·leetcode
王老师青少年编程4 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【线性扫描贪心】:均分纸牌
c++·算法·编程·贪心·csp·信奥赛·均分纸牌
EQUINOX14 小时前
2026年码蹄杯 本科院校赛道&青少年挑战赛道提高组初赛(省赛)第一场,个人题解
算法
萝卜小白4 小时前
算法实习Day04-MinerU2.5-pro
人工智能·算法·机器学习
Liangwei Lin4 小时前
洛谷 P3133 [USACO16JAN] Radio Contact G
数据结构·算法
weixin_513449964 小时前
PCA、SVD 、 ICP 、kd-tree算法的简单整理总结
c++·人工智能·学习·算法·机器人
code_pgf4 小时前
Qwen2.5-VL 算法解析
人工智能·深度学习·算法·transformer