在 React 中其实是 render 函数中return 的内容会生成 DOM,return 中的内容由两部分组成,一部分是 JSX ,另外一部分就是 state 中的数据,因此简单来说,在 React 中 JSX 结合 state 就生成了 DOM。html
如今抛开虚拟 DOM 不谈,若是让咱们去实现 React 中当数据发生变化时如何操做 DOM 来实现页面内容的变化,咱们会怎样去实现?算法
第一种方案:数组
1)JSX + state 生成真实的 DOM,并显示在页面上浏览器
2)state 发生变化dom
3)此时 JSX + state 再次结合生成新的真实的 DOM异步
4)新的 DOM 直接替换掉原来的 DOM函数
这样页面会发生变化,可是生成真实的 DOM 和在页面上再从新加载新的 DOM 都比较耗性能。性能
第二种方案:spa
1)JSX + state 生成真实的 DOM,并显示在页面上code
2)state 发生变化
3)此时 JSX + state 再次结合生成新的真实的 DOM
4)新的 DOM 和原始的 DOM 做对比,找出差别
5)利用找出的差别,替换掉页面上原始 DOM 的相应部分
此时页面也会发生变化,和方案一相比多了对比步骤可是只须要替换掉原始DOM的一部分便可,综合来讲,方案二要优于方案一。
第三种方案
1)JSX + state 生成虚拟 DOM(虚拟 DOM 就是一个 JS 对象,用它来描述真实 DOM)
例以下面这段代码:
<div id='abc'>item</div>
注意上面的 div
,span
标签时 JSX 语法,并非真实的 DOM,这里是先生成虚拟 DOM ,而后再下一步的时候才用虚拟 DOM 生成真实的 DOM,由 JSX 到真实的 DOM 中间有一个虚拟 DOM。
JSX -> 虚拟DOM(JS对象) -> 真实DOM
也就是说,JSX 须要先转换为 JS 对象,而后再转换为真实的 DOM。
生成的虚拟 DOM 为
['div',{id: 'abc'}, 'item']
虚拟 DOM 的格式为
['标签名',标签属性对象,子标签]
那么 <div id='abc'>item</div>
是如何转化为 JS 对象的呢?
实际上在 React 中上面这样写就至关于下面这样写:
React.createElement('div', {id: 'abc'}, 'item');
那么实际上就算是没有 JSX 语法经过上面这样写也是能够的,可是会很是不方便。
2)用虚拟 DOM 的结构生成真实的 DOM 显示在页面上。
3)JSX + state 生成新的虚拟 DOM
4)两个虚拟 DOM 进行对比,找出差别
5)根据差别直接修改替换页面上的 DOM
虚拟 DOM 是一个 JS 对象,生成一个虚拟 DOM 比生成一个真实的 DOM 结构要容易省时地多,并且两个虚拟 DOM(JS 对象) 之间的对比也比较简单,因此方案三最佳。
React 中使用的也是第三种方案的思想。
那么虚拟DOM的优势到底有哪些呢?
1)性能提高
这一点经过上面的比较就能够看得出来
2)使得跨端应用得以实现,例如原生应用。
React Native 可以作原生应用虚拟 DOM 是很重要的一方面,原生应用中是没有 DOM 这个概念的,DOM 是浏览器中存在的,可是有了虚拟 DOM(JS 对象) 以后,在原生应用中就能够将虚拟 DOM(JS 对象) 转换为一些原生应用中可以支持的原生组件在原生应用中显示。
使用虚拟 DOM 时很重要的一个步骤就是两个虚拟 DOM 之间的比较,那么怎样去进行比较呢?
React 中采用 diff 算法,简单来讲主要有如下三个方面:
1)当短期内连续调用屡次 setState 时,React 只会进行一次虚拟 DOM 的比对。
咱们知道当 state 或者 props 发生变化时,页面会发生变化,实际上 props 的变化也是由于父组件 state 的变化,因此当页面发生变化时其实是调用 setState 致使数据发生变化变化时。当短期内连续调用屡次 setState 时,若是每次都进行一次虚拟 DOM 的比对,那么性能会比较低,反之屡次调用 setState 只进行一次虚拟 DOM 的比对会提高性能。这也是为何 setState 要设置成异步的缘由,由于若是同步的话当执行完一次 setState 时就会发生一次虚拟 DOM 的比对。(同步是顺序当即执行,异步是当全部的同步程序执行完后再执行)
2)在比较虚拟 DOM 时采用逐层同层比较,当上一层出现差别时,那么下面的各层就不须要再比较了,下面各层的 DOM 都将被新的 DOM 替换。
这样作看起来,复用性不是很好,由于下面各层有可能会有许多相同的 DOM。可是这样作会使得比较算法很是简单,比较的速度很是快。
3)设置 key 值
假设如今有一个数组 [a, b, c]
遍历每一项显示在页面上,如今数组发生变化将第一项 a 删掉,若是没有 key 值,数组 [b, c]
没法和原数组进行比对,例如 b 到底和原数组的哪个进行比较呢?
可是如今假设有了 key 值,原数组中 a 的 key 值是 a,b 的 key 值是 b,c 的 key 值是 c。删除 a 以后,经过 key 值,b 的 key 值 b 在原数组中找到 b,说明 b 没有发生变化,c 同理也没有发生变化,可是原数组中的 a 在新数组中并无找到,说明新数组中将 a 删掉了,因此在操做页面时将 a 删掉便可。
这里有一点须要注意的是,key 值必定要选不能变化的,利用数组的索引来作 key 值就不可取。仍是以上面为例进行说明。原数组的 a 的 key 值是 0,b 的 key 值是 1,c 的 key 值是 2,删掉 a 后,新数组的 b 的 key 值是 0,c 的 key 值是 1,通过比对原数组的 a 和新数组的 b key 值相同,虚拟 DOM 会认为它们是相同的,没有差别,可是实际上它们是不一样的。