React DOM Diff

背景

React 组件是一个返回 Virtual DOM Tree 的方法

function SomeComponent() {
   
  return (
  	<div>
      <span>..</span>
      <Button>..</Button>
    </div>
  )
}

//* 上述代码会转换为一个 React Virtual DOM,就像如下所示
class VirtualDOMNode {
   
  type: SomeComponent // 如果是内置的就会描述成 "div" 等等
  children
  update() {
   
    const newVirtualDOM = this.type()
    // ...
  }
}

更新策略有很多,而最划算的更新策略就是在原来的基础上进行新增、删除等操作,而不是对整个DOM进行替换。

大体过程

1. React 渲染函数执行时会生成一个树状结构

<div style={
   xxx}>...</div>

// 上面的jsx会转换为
React.createElement('div', {
   
  style: {
   xxx},
  ...
})

createElement() 被调用之后将生成一个 Virtual DOM 节点

class ReactElement {
   
  type: SomeComponent
  props: {
   
    children
  }
}

Element 本身可以看做是对“数据”的描述,也就是元数据,它本身是没有行为的。

Element 可以看做是虚拟 DOM,因为它代表了真实的 DOM 结构。但是又因为它本身没有行为,所以要使它拥有像更新的能力,就得对该 Element 再进行一次封装 (FiberNode)。

class FiberNode {
   
  type: SomeComponent // 函数组件本身,调用它的时候可以生成新的 Element,复制于 ElementNode 
  props: {
   },
  update() {
   }
}

2. 更新

对于某个给定组件

function SomeComponent() {
   
 	return <div>...</div>
}

当组件 SomeComponent 触发更新时,React 会这样处理

// Fiber Context
{
   
  let vDOMOld // 上一次调用 SomeComponent 产生的 VirtualDOM
  //...
  
  update() {
   
    const vDOMNext = SomeComponent()
    const updates = domDiff(vDOMOld, vDOMNext)
    vDOMOld = vDOMNext
    apply(updates)
  }
}

React 更新产生虚拟 DOM 节点,然后通过 diff 算法比较两个 DOM 节点的差异,然后决定更新步骤,最后再向 DOM 应用这些更新。

从上述伪代码中可以看到所有的更新都依赖 diff,这就要求 diff 算法的效率必须足够高才能很好地支撑起整个项目。

FAQ

问:为什么不把更新方法放到 ElementNode 当中

答:因为组件的更新需要在特定的场景下,它可能是在浏览器端、Native等等;另外组件的更新涉及到特殊的算法,像 Fiber。在具体的场景下再封装具体的方法有利于代码的设计。

细节

对于相同类型的节点

if (vDOMOld.type === vDOMNext.type) {
   
  // ...
}

比如下述组件发生变化时

function Button({
    text}) {
   
  return <button>{
   text}</button>
}

只需要替换属性即可

<Button text="点击" />
// 转换为
<Button text="click" />

对于不同类型的节点

当遇到不同类型的节点时,React 会直接替换而不是继续往下比

你可能感兴趣的:(#,React源码,react.js,javascript,前端,diff)