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

摘要

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

<div>
	<p>
 		<span></span>
	</p>
</div>

如果对于多节点的情况:

<ul>
	<li></li>
	<li></li>
	<li></li>
</ul>

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

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

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算法的处理。

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方法

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进行递归处理。

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的处理中,也要考虑,当前的操作是更新还是替换。

function completeHostText(filberNode) {
    
    
  //其他代码。。。。。
  if(parent && parent.stateNode && parent.tag === HostComponent) {
    
    
    if(!parent.stateNode) {
    
    
      parent.stateNode.appendChild(element);
    }else{
    
    
      parent.stateNode.replaceChildren(element);
    }
  }
  //其他代码。。。。
}

猜你喜欢

转载自blog.csdn.net/weixin_46726346/article/details/132406741
今日推荐