虚拟DOM、diff算法、fiber

在 React 中,Virtual DOM(虚拟 DOM)是一种内存中的表示,它是由 React 创建和维护的一种树形结构,用于表示真实 DOM 的抽象。当状态发生变化时,React 会通过比较新的 Virtual DOM 和旧的 Virtual DOM 的差异来确定需要更新的部分,并将这些变化应用到真实的 DOM 中,从而实现页面的更新。

Virtual DOM 的引入带来了以下好处

  1. 提高性能
    Virtual DOM 通过将真实 DOM 的操作转换为对虚拟 DOM 的操作,然后批量更新到真实 DOM 中,减少了对真实 DOM 的频繁操作。这样可以提高页面渲染的性能,减少了浏览器重绘和重排的次数,提升了用户体验。
  • 减少 DOM 操作的次数:虚拟 DOM 可以将多次操作合并为一次操作,比如添加 1000 个节点,却是一个接一个操作的;
  • 减少 DOM 操作的范围:虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如添加 1000 个节点,其实只有 10 个是新增的。
  1. 简化开发
    使用 Virtual DOM 可以使开发人员更专注于应用程序的逻辑和数据,而不必过多地关注 DOM 操作的细节。通过使用组件化开发模式,开发人员可以将 UI 拆分成小而独立的组件,每个组件都有自己的状态和行为,这样可以更容易地管理和维护应用程序的代码。

  2. 跨平台兼容性
    由于 Virtual DOM 是一个与平台无关的抽象层,因此可以轻松地将 React 应用程序移植到不同的平台上,包括 Web、移动端和桌面端等。这样可以提高应用程序的可移植性和跨平台兼容性。

  3. 方便的测试
    使用 Virtual DOM 可以方便地进行单元测试和集成测试,因为可以在内存中操作虚拟 DOM,而无需真实的浏览器环境。这样可以加快测试速度,提高开发效率。

虚拟 DOM 长什么样子?

react:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const vNode={
key:null,
props:{
children:[
{type:'span',...},
{type:'span',...},
],
className:'red',
onClick:()=>{}
},
ref:null,
type:'div',
...
}

vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const vNode={
tag:"div",
data:{
class:'red',
on:{
click:()=>{}
}
},
children:[
{tag:'span',...},
{tag:'span',...},
],
...
}

实现虚拟 DOM 例子

  1. 定义虚拟 DOM 元素
  2. 创建文本节点
  3. 将虚拟 DOM 渲染为真实 DOM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 定义虚拟 DOM 元素
const createElement = (type, props, ...children) => {
return {
type,
props: {
...props,
children: children.map((child) =>
typeof child === "object" ? child : createTextElement(child)
),
},
};
};

// 创建文本节点
const createTextElement = (text) => {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
};

// 将虚拟 DOM 渲染为真实 DOM
const render = (element, container) => {
const dom =
element.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type);

const isProperty = (key) => key !== "children";
Object.keys(element.props)
.filter(isProperty)
.forEach((name) => {
dom[name] = element.props[name];
});

element.props.children.forEach((child) => render(child, dom));
container.appendChild(dom);
};

// React 的 createElement 和 render 函数使用
const MyReact = {
createElement,
render,
};

// 使用示例
const element = MyReact.createElement(
"div",
{ id: "container" },
MyReact.createElement("h1", null, "Hello, World!"),
MyReact.createElement("p", null, "This is a paragraph.")
);

const container = document.getElementById("root");
MyReact.render(element, container);

虚拟 DOM 的缺点

需要额外的创造函数,如 createElement 或 h,但可以通过 JSX 来简化 XML 写法,严重依赖打包工具

diff 算法

  1. 虚拟 DOM 树比较:在进行更新时,React 会将当前的虚拟 DOM 树与新的虚拟 DOM 树进行比较,找出差异。

  2. 差异检测:React 使用 深度优先搜索算法(DFS)对比两棵虚拟 DOM 树,找出两棵树之间的差异。它会逐个比较节点及其子节点,找出哪些节点需要更新、添加或删除。

  3. Diff 策略:React 使用了一些优化策略来减少 DOM 操作次数:

  • 同层比较:React 只会对比相同层级的节点,不会跨层级比较。
  • 唯一标识:在进行节点比较时,React 会使用节点的唯一标识(通常是 key 属性)来判断节点是否相同,从而避免不必要的更新操作。
  • 节点移动:React 会尽量复用已存在的节点,而不是重新创建新的节点。如果节点顺序改变,React 会尝试通过移动节点来达到更新效果,而不是直接删除和重新插入节点。
  • 批量更新:React 会将多个更新操作合并成一个批量更新,然后一次性更新到真实 DOM 中,以减少页面重绘和重排的次数。
  1. 应用差异:一旦找到了两棵虚拟 DOM 树之间的差异,React 就会根据这些差异来进行相应的操作,更新真实 DOM。

vue 中的 diff 算法在对新旧虚拟 dom 对比时,是从节点的两侧向中间对比,如果节点的 key 值和元素类型相同,属性值不同,就认为是不同节点,会将该节点删除重建
react 中的 diff 算法在对新旧虚拟 dom 对比时,是从节点的左边向右边对比,如果节点的 key 值和元素类型相同,属性值不同,就认为是节点同类型,只修改当前节点的属性

逻辑

  1. Tree diff
  • 将新旧两棵树逐层对比,找出哪些节点需要更新
  • 如果节点是组件就看 Component diff
  • 如果节点是标签就看 Element diff
  1. Component diff
  • 如果节点是组件,就先看组件类型
  • 类型不同直接替换(删除旧的)
  • 类型相同则只更新属性
  • 然后深入组件做 Tree diff(递归)
  1. Element diff
  • 如果节点是原生标签,则看标签名
  • 标签名不同直接替换,相同则只更新属性
  • 然后进入标签后代做 Tree diff(递归)

dom diff 有什么缺点

同级比较,存在 BUG,需要加个 唯一 Key,不能用 index 做下标

Fiber

Fiber 是 React v16 中引入的一种新的协调算法,用于调度和管理 React 的渲染和更新过程。Fiber 的目标是使 React 应用程序更加流畅和响应,减少页面卡顿和掉帧的情况。
Fiber 的核心思想是将 React 的渲染过程分解为可中断的小任务,并且可以根据优先级来调度这些任务的执行顺序。这样可以使 React 在执行渲染任务时更加灵活,可以根据页面的需要来调整任务的优先级和执行顺序,从而提高用户体验。

Fiber 的引入使得 React 具备了更高的并发能力和更灵活的调度策略,使得 React 应用程序可以更好地适应不同的场景和用户需求。

综上所述,Virtual DOM 和 Fiber 是 React 内部机制中的两个关键概念,它们共同作用于 React 的渲染和更新过程中,提高了 React 应用程序的性能、效率和响应能力。Virtual DOM 优化了对真实 DOM 的操作,而 Fiber 则优化了 React 的渲染和更新调度过程。

关于 DOM 的谣言

DOM 操作慢?虚拟 DOM 快

这句话类似于:刘翔矮(对比于姚明)
DOM 操作慢是对比于 JS 原生 API,如数组操作
任何基于 DOM 的库(Vue/React)都不可能再操作 DOM 时比 DOM 快

为什么网上有这样的谣言?

因为在某些情况下,虚拟 DOM 快

规模太大的时候,比如 100000 个节点,原生 DOM 要快,因为虚拟 DOM 要做大量的计算,规模小的时候(1000 个节点),虚拟 DOM 快

在测试插入 10000 个节点的时候,vue 要比 react 快很多,vue 的速度接近原生 JS,当然这两个都没有做任何优化