虚拟DOM和DOM diff 记录 (增加fiber)

面试常见问题,特地记录一下。

代码已经关联到github: 链接地址 觉得不错可以顺手点个star,这里会持续分享自己的开发经验(:

什么是虚拟DOM

与真实DOM对应,一个能表示真实DOM的对象。

react下的虚拟DOM

格式:

const vNode = {
  key:null,
  props:{
  children:[{type:'span'}]
  },
  className:"",
  onClick: () => {},
  type:'div'
}

代码创建:
使用 React.createElement 或者 jsx

React.createElement('div',{className:"",onClick: () => {}},[
  //child...
 ])

DOM操作慢?虚拟DOM快?

二者其实不能简单的概括,因为虚拟DOM最终还是会操作真实DOM。

  • 真实DOM操作慢是对比于js原生的api,如数组操作。
  • 任何基于DOM的库(React、Vue),因为都会操作DOM,所以不会比真实DOM快。
  • 正常节点较少时,虚拟DOM会比真实的DOM渲染快(不是操作快,也就是不可交互时间短),而当节点太多时,如10W,则真实的DOM操作更快。

那为什么还会有人说DOM慢,虚拟DOM快呢?因为在某些情况下,虚拟DOM确实比真实DOM快。首先,我们要理解一个概念,JS计算比操作dom更快

  1. 减少DOM操作
    1. 合并DOM操作,减少DOM操作次数。(增加1000个节点,计算1000次,插入一次)
    2. 虚拟DOM借助DOM diff可以把多余的操作省略,减少DOM操作的范围(增加1000个节点,比较后,只有10个是新增的)
  2. 跨平台

虚拟DOM本质上是js对象,所以可以转化成如小程序的组件,安卓的视图等等。

react和JS控制原生DOM的差异

事件(如 click )
  • js的点击事件是监听在对应dom上的,当然也可以通过事件委托给直接委托给父辈元素,
  • react的点击事件,是监听在虚拟dom上的,而react的事件是合成事件,可以理解为委托到window的事件
属性(如class style)
  • js是直接去修改对应的属性
  • react则是通过props或者state的改变,触发重新渲染,domDiff,合并修改,修改属性。

DOM diff

策略
  1. Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。
  2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
  3. 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
核心流程

一个函数,旧版(16之前)是为 patch

patches = patch(oldVNode,newVNode)

新版为核心函数为: reconcileChildFibers

reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {}

旧的fiber对象与新的ReactElement对象相比较,输出新的fiber

新版DOM diff 逻辑

构造Fiber树的方式。

比较顺序
  1. 开始之前,markUpdateLaneFromFiberToRoot,判断从该节点到root节点,哪些节点需要更新,设置更新通道childLane
  2. 标签(或者组件)属性 【memoizedProps!==pendingProps】,标记didReceiveUpdate为true,只有meno等组件需要。
  3. 父元素是否需要更新(markUpdateLaneFromFiberToRoot中得到的childLane),如果不需要就直接跳过,往下遍历子或者兄弟元素
  4. 标签(或者组件)类型 【type和key是否一样】,是否复用组件
  5. 父元素比较完,往下比较子或者兄弟元素(通过workLoop,注意是通过类型链表的方式,这里会神奇的再次调用beginWork来走第2步)
比较逻辑
逻辑
  1. diff
    1. 比较props,如果不一样则标记didReceiveUpdate
    2. 先看当前的Fiber节点是否需要更新,需要则直接进行调和,不需要则比较子节点,最后都返回子节点。
  2. reconcileChildFibers
    1. 先判断传入的元素类型,是对象、数组还是字符串
    2. 不同元素类型分别比较
    3. 核心都是比较key和type,类型或者key不同直接替换(删除旧的stateNode节点,新建stateNode,注意fiber节点都是新的)

数组节点:

  1. 第一次循环遍历两个数组的相同key部分,key一样就会复用,如果一开始的key就不一致,就会直接跳出循环
  1. 第二次循环遍历两个数组的不同key部分,key一样就会复用

文本节点:只要旧的节点第一个不是文本节点就直接新建

  1. 如果相同则复用
  2. 完成本次比较,返回节点,同时会再次进行一遍步骤1,因为已经比较完了,所以这次会进入子节点或者兄弟节点的diff。
    1. 如果有下个节点就继续比较
    2. 没有就完成当前子树的遍历, completeUnitOfWork
旧版DOM diff 逻辑
比较顺序
  1. 标签(或者组件)类型
  2. 标签(或者组件)属性
  3. 子元素递归
比较逻辑
  1. Tree diff
    1. 新旧两棵树逐层对比,找出哪些节点需要更新
    2. 如果是组件节点就看 Component diff
    3. 如果是标签节点就看 Element diff
  2. Component diff
    1. 如果是组件节点,先比较组件类型
    2. 类型不同直接替换(删除旧的)
    3. 如果相同只更新属性
    4. 深入组件 Tree diff
  3. Element diff
    1. 原生节点,看标签名
    2. 标签名不同直接替换,相同则只更新属性
    3. 深入标签后代 Tree diff
DOM diff例子(Key的作用)
  1. 两个子元素,删除后一个(不存在key的情况)。

    标签类型和标签属性不变,不用更新;子元素从[1,2]变成了[2],1标签没变,但是children变了,更新内容(子元素2的内容放到了这边);子元素2不见了,删除对应dom。

  2. 两个子元素,删除后一个(存在key的情况)。

标签类型和标签属性不变,不用更新;子元素从[1,2]变成了[2],但是因为存在key,计算机知晓是 key:1 的元素删除了,2不变,所以会直接删除1,保留2.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值