React -- (3) 虚拟DOM and diff算法

79 阅读3分钟

一. JSX 与 虚拟DOM

JSX JSX 是一个看起来很像 XML 的 JavaScript 语法扩展,其本质是 createElement()方法的语法糖 (语法糖:更加直观、简洁、友好)。

虚拟DOM就是一个 JS 对象,用来描述我们希望在页面中看到的 HTML 结构内容.

<1> Why 使用虚拟DOM?

真实 DOM 对象属性多,处理起来繁琐、效率低。更重要的原因:React 要做跨平台开发,而不是被束缚在浏览器端。

如下:我们打印以下一个普通真实 DOM对象 的属性

2179.png

但是,这些属性,我们肯定不会都用到,真正用到的很少,所以我们可以用一个 JS 对象,来代替该真实 DOM 对象:

2180.png

该对象有三个属性:

  • tagName: 用来表示这个元素的标签名。
  • props: 用来表示这元素所包含的属性。
  • children: 用来表示这元素的children。
<2> React 使用 JSX 语法的转换过程:

JSX 代码会经过babel-loader 会解析为 React.createElement()嵌套对象。React.createElement() 创建的就是一个虚拟DOM结构。

2177.png

会被转化为:

2178.png

栗子:

2175.png

二. diff 算法 【Reconciliation协调】

算法实现步骤有三步:

  • 【首次渲染】用JavaScript对象来表示DOM树的结构; 然后用这个树构建一个真正的DOM树插入到文档中
  • 当状态变更的时候,重新构造一个新的对象树,然后用这个新的树和旧的树作对比,记录两个树的差异。 
  • 把2所记录的差异应用在步骤一所构建的真正的DOM树上,视图就更新了。

【首次渲染】

2181.png

【更新】 2182.png

<2> 比较两棵树差异的过程

前提:

当状态更新时,react会依据改动后的JSX代码,重新构建一棵 JS 虚拟DOM树,然后将其与之前的老的 JS 虚拟DOM树进行比较!!!

(1) 对两棵树深度优先遍历,记录差异

2183.png

在深度优先遍历的时候,每遍历到一个节点就把该节点和新的树进行对比,如果有差异的话就记录到一个对象里面。

  • 比如div和新的div有差异,当前的标记是0, 那么我们可以使用数组来存储新旧节点的不同
patches[0] = [{difference}, {difference}, ...]
  • 同理使用patches[1]来记录p,使用patches[3]来记录ul,以此类推
(2) 差异的类型

1> 替换原来的节点 

对于根节点的替换,react 会销毁旧树,重建新树(真实DOM树)

2> 移动、删除、新增子节点

  • 在结尾插入节点

可以直接添加!!!只需改变该插入节点,前面都不用变!!!

  • 在开始位置插入

需要改变每一个节点,为了解决该性能问题,就引入了key属性!!!

2184.png

           - 说明:key 属性在 React 内部使用,但不会传递给你的组件
           - 推荐:在遍历数据时,推荐在组件中使用 key 属性:<li key={item.id}>{item.name}</li>
           - 注意:key 只需要保持与他的兄弟节点唯一即可,不需要全局唯一
           - 注意:尽可能的减少数组 index 作为 key,数组中插入元素的等操作时,会使得效率低下

3> 修改了节点的属性

直接修改就好

4> 修改文本节点的内容

可以给对应的类型,创建不同的变量来记录!!

2186.png

则对应的差异记录就可以写为:

2187.png

(3) 把差异引用到真实的 DOM 树上,通过相应的DOM操作进行修改