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

摘要

经过之前的几篇文章,我们已经实现了一个可以进行更新渲染的假React。但是如果我们把我们的jsx修改成这样:

javascript 复制代码
function App() {
  const [age, setAge] = useState(20)
  const click = function() {
    setAge(age + 1)
  }
  return age % 2 === 0 ? jsx("div", {
      key: 'div1',
      children: jsx("span", {
        key: 'span',
        children: 'div1',
        onClick: click
      })
    }) : jsx("div", {
      key: 'div1',
      children: jsx("span", {
        key: 'span',
        children: 'div2',
        onClick: click
      })
    })
}

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

虽然页面的效果是正确的。但对于两个jsx,二者的区别仅仅是span标签里面的内容不同。

但是在程序里面,我们是相当于每次都重新beginWork,重新的创建Filber树,重新的创建真实DOM。

而对于这里div和span标签,它没有任何的改变,我们是否可以用一种优化策略,从而对旧资源进行利用呢? 所以Diff由此而来。

这一篇先只说单节点的Diff,因为目前还没实现带有sibling的情况。

1.修改beginWork

我们回顾一下在beginWork里面干了什么。在将jsx转换为ReactElement后,我们会通过beginWork来构建一颗Filber树。

那如果,对于可复用的FilberNode,我们是否可以不去创建,直接复用呢?

可以的,那对于React来说,什么节点是可复用的呢:

如果旧的FilberNode和新的ReactElement
key相同,type相同。

那么就是可以复用的。

所以在beginWork中的reconcileChildren方法里,如果我们发现上面的情况,我们就不会创建新的FilberNode。

javascript 复制代码
function reconcileChildren(parent,element) {
  const newChild = diffReconcileChildren(parent, element);
  if(newChild) {
    return newChild
  }
  //其他代码。。。
  return filberNode
}

2.实现diffReconcileChildren方法

该方法接受两个参数,第一个是父节点,第二个是新的ReactElement。

(1)我们要先拿到父节点的child,比较child和element的key和type。

(2)将element保存在child的memoizedState里面。

(3)然后其他逻辑和reconcileChildren里的一样即可。

(4)直接返回child。

javascript 复制代码
function diffReconcileChildren(filberNode, element) {
  const child = filberNode.child;
  if(child && child.key && child.type) {
    if(child.key === element.key && child.type === child.type) {
      child.memoizedProps = element;
      child.pendingProps = element.props;
      if(child.tag === HostText) {
        child.pendingProps = element
      }
      return child;
    }
  }
}

这里为什么要将element保存在memoizedState里面,是因为虽然节点没有改变,但是子节点可能有改变,或者属性会有改变。所以要在后面的completeWork里进行处理。

3.修改completeWork

经过上面的处理后,FilberNode不会无脑的重复创建,而是复用了。而completeWork的工作,主要是创建真实DOM,挂载在FilberNode的stateNode上。

所以在completeWork中,也不能无脑的创建真实DOM,而是也要判断是否是可复用的。

javascript 复制代码
export const completeWork = (filberNode) => {
  const tag = filberNode.tag
  switch (tag) {
    case HostComponent: {
      if(filberNode.stateNode !== null){
        //更新
        updateCompleteHostComponent(filberNode)
      }else{
        completeHostComponent(filberNode)
      }
      break;
    }
    //其他代码。。。。
  }
}

所以在对HostComponent的处理上,如果发现不是mount阶段,就要判断是否需要复用旧的。

4.实现updateCompleteHostComponent方法

在该方法中,我们接受filberNode。同时我们可以拿到它的memoizedState(在beginWork中传过来的)。

再判断一下key和type,如果依旧相同,那么说明是可复用的。我们直接不创建新的DOM即可。

javascript 复制代码
function updateCompleteHostComponent(filberNode) {
  const element = filberNode.memoizedProps;
  if(element.key === filberNode.key && element.type === filberNode.type) {
    addPropsToDOM(filberNode.stateNode, filberNode.pendingProps)
  }else{
    const type = filberNode.type;
    const element = document.createElement(type);
    addPropsToDOM(element, filberNode.pendingProps)
    filberNode.stateNode = element;
    const parent = filberNode.return;
    if(parent && parent.stateNode && parent.tag === HostComponent) {
      parent.stateNode.appendChild(element)
    }
  }
  completeWork(filberNode.child)
}

addPropsToDOM方法是我们在实现事件机制的时候,需要调用的方法。

OK,这样我们就实现了单节点的Diff算法。

相关推荐
程序员爱技术12 分钟前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
悦涵仙子1 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
兔老大的胡萝卜1 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
pianmian13 小时前
python数据结构基础(7)
数据结构·算法
cs_dn_Jie5 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
好奇龙猫5 小时前
【学习AI-相关路程-mnist手写数字分类-win-硬件:windows-自我学习AI-实验步骤-全连接神经网络(BPnetwork)-操作流程(3) 】
人工智能·算法
开心工作室_kaic5 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿6 小时前
webWorker基本用法
前端·javascript·vue.js
sp_fyf_20246 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
香菜大丸6 小时前
链表的归并排序
数据结构·算法·链表