Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 虚拟 DOM Diff 阅读笔记 #24

Open
h476564406 opened this issue Feb 16, 2019 · 0 comments
Open

React 虚拟 DOM Diff 阅读笔记 #24

h476564406 opened this issue Feb 16, 2019 · 0 comments

Comments

@h476564406
Copy link
Owner

h476564406 commented Feb 16, 2019

源文章 深入浅出 React(四):虚拟 DOM Diff 算法解析
具体算法: 虚拟dom: snabbdom源码解析

假设:

  1. 两个相同组件产生类似的 DOM 结构,
    不同的组件产生不同的 DOM 结构;
  2. 对于同一层次的一组子节点,它们可以通过唯一的 id 进行区分。

一. 两棵树逐层(同层)比较

React只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉, 而不是将该节点移动过去。

React 只会考虑同层节点的位置变换,对于不同层的节点,只有简单的创建和删除。
当根节点发现子节点中 A 不见了,就会直接销毁 A。
当 D 发现自己多了一个子节点 A,则会创建一个新的 A 作为子节点。
因此对于这种结构的转变的实际操作是:

A.destroy();
A = new A();
A.append(new B());
A.append(new C());
D.append(A);

以 A 为根节点的树被整个重新创建。

这也给我们一个提示,在实现自己的组件时,保持稳定的 DOM 结构会有助于性能的提升。例如,我们有时可以通过 CSS 隐藏或显示某些节点,而不是真的移除或添加 DOM 节点。

二. 同一层不同组件
当在树中的同一位置前后输出了不同类型的组件

renderA: <Header />
renderB: <Content />
=> [removeNode <Header />], [insertNode <Content />]

当 React 在同一个位置遇到不同的组件时,简单的销毁第一个组件,再把新创建的组件加上去。

三. 同一层不同节点类型
当在树中的同一位置前后输出了不同类型的节点
当一个节点从 div 变成 span 时,直接删除 div 节点,再插入一个新的 span 节点。

renderA: <div />
renderB: <span />
=> [removeNode <div />], [insertNode <span />]

四. 同一层相同类型节点
React 会对属性进行重设从而实现节点的转换。

renderA: <div style={{color: 'red'}} />
renderB: <div style={{fontWeight: 'bold'}} />
=> [removeStyle color], [addStyle font-weight 'bold']

五. 同一层列表节点

列表节点的操作通常包括添加、删除和排序。例如下图,我们需要往 B 和 C 直接插入节点 F,在 React 中,只会告诉 React 新的界面应该是 A-B-F-C-D-E,由 Diff 算法完成更新界面。

这时如果每个节点都没有唯一的标识,React 无法识别每一个节点,那么更新过程会很低效,即,将 C 更新成 F,D 更新成 C,E 更新成 D,最后再插入一个 E 节点。三次更新,一次插入。效果如下图所示:

可以看到,React 会逐个对节点进行更新,转换到目标节点。而最后插入新的节点 E,涉及到的 DOM 操作非常多。而如果给每个节点唯一的标识(key),那么 React 能够找到正确的位置去插入新的节点。一次插入。

对于列表节点顺序的调整其实也类似于插入或删除,下面结合示例代码我们看下其转换的过程。仍然使用前面提到的示例:https://supnate.github.io/react-dom-diff/index.html ,我们将树的形态从 shape5 转换到 shape6:

即将同一层的节点位置进行调整。如果未提供 key,那么 React 认为 B 和 C 之后的对应位置组件类型不同,因此完全删除后重建,控制台输出如下:

shape5: function() {
  return (
    <Root>
      <A>
        <B />
        <C />
      </A>
    </Root>
  );
},

shape6: function() {
  return (
    <Root>
      <A>
        <C />
        <B />
      </A>
    </Root>
  );
},
B will unmount.
C will unmount.
C is created.
B is created.
C did mount.
B did mount.
A is updated.
R is updated.

而如果提供了 key,如下面的代码:

shape5: function() {
  return (
    <Root>
      <A>
        <B key="B" />
        <C key="C" />
      </A>
    </Root>
  );
},

shape6: function() {
  return (
    <Root>
      <A>
        <C key="C" />
        <B key="B" />
      </A>
    </Root>
  );
},

那么控制台输出如下:

C is updated.
B is updated.
A is updated.
R is updated.

可以看到,对于列表节点提供唯一的 key 属性可以帮助 React 定位到正确的节点进行比较,从而大幅减少 DOM 操作次数,提高了性能。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant