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);
    }
  }
  //其他代码。。。。
}
相关推荐
old_power20 分钟前
【PCL】Segmentation 模块—— 基于图割算法的点云分割(Min-Cut Based Segmentation)
c++·算法·计算机视觉·3d
fmdpenny21 分钟前
Vue3初学之商品的增,删,改功能
开发语言·javascript·vue.js
Bran_Liu33 分钟前
【LeetCode 刷题】字符串-字符串匹配(KMP)
python·算法·leetcode
小美的打工日记34 分钟前
ES6+新特性,var、let 和 const 的区别
前端·javascript·es6
涛ing36 分钟前
21. C语言 `typedef`:类型重命名
linux·c语言·开发语言·c++·vscode·算法·visual studio
涔溪1 小时前
有哪些常见的 Vue 错误?
前端·javascript·vue.js
程序猿online1 小时前
前端jquery 实现文本框输入出现自动补全提示功能
前端·javascript·jquery
Jcqsunny1 小时前
[分治] FBI树
算法·深度优先··分治
黄金小码农1 小时前
C语言二级 2025/1/20 周一
c语言·开发语言·算法
謓泽2 小时前
【数据结构】二分查找
数据结构·算法