React提高04 对虚拟DOM和加载过程的理解
也是面试时比较常遇到的React的问题之一。了解了之后还是能够加深对React的理解。

React虚拟DOM的理解
(1)用JavaScript对象结构表示DOM树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
(2)当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
(3)把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了
虚拟DOM本质上就是在JS和DOM之间做了一个缓存。可以类比CPU和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然DOM这么慢,我们就在JS和DOM之间加个缓存。CPU(JS)只操作内存(虚拟DOM),最后的时候再把变更写入硬盘(DOM)。
虚拟DOM是用JS的对象结构模拟出HTML中DOM的结构,批量的增删改查,由于直接操作的是JS对象,所以速度要比操作真实DOM要快,最后更新到真正的DOM中
虚拟DOM构建的对象,除了DOM相关属性,还包括了React自身需要的属性,比如ref,key等,大概如下结构:
| 1 | { | 
React何时将虚拟DOM渲染为真实DOM
render这个步骤就是React组件挂载的步骤
React组件挂载:将组件渲染,并构建DOM元素然后插入页面的过程
render的步骤是创建虚拟DOM,挂载组件,
在render之后,将这个虚拟DOM树真正渲染成一个DOM树,插入了页面,可以认为是在componentDidMount这个步骤完成的,该方法被调用时,已经渲染出真实的DOM
在组件存在期,componentDidUpdate与componentDidMount类似,在组件被重新渲染后,此方法被调用,真实DOM已经渲染完成
React不能setState的步骤
shouldComponentUpdate和componentWillUpdate就会造成循环调用,使得浏览器内存占满后崩溃
对于setState的理解
setState是一个异步方法,一个生命周期内的所有setState方法会合并操作。
在各个生命周期中执行setState:
(1)在componentWillMount执行setState是无意义的,应该将这里为state的赋值放到constructor中直接作为state的初始值。
这是因为,组件直挂再一次,在componentWillMount里面setState会但是仅仅会更新state一次,而且会和constructor中的初始化state合并执行。所以这是无意义的setState
(2)在componentDidMount中执行setState会导致组件在初始化的时候就触发了更新,渲染了两遍,应该尽量避免。
有一些场景,比如在组件DOM渲染完成后获得DOM元素位置或者宽高等等设置为state,会不得在componentDidMount之后setState,但是除了这些必要的时候,都应该尽量避免在componentDidMount里setState。
(3)在componentWillUnmount中执行setState不会更新state,是不生效而且无意义的。
(4)禁止在shouldComponentUpdate和componentWillUpdate中调用 setState,这会造成循环调用,直至耗光浏览器内存后崩溃。
在shouldComponentUpdate和componentWillUpdate调用setState会再次触发这两个函数,然后在两个函数又触发了setState,然后再次触发这两个函数,这样就进入了一个不停setState然后不停触发组件更新的死循环里,会导致浏览器内存耗光然后崩溃。
(5)在componentDidUpdate中执行`setState同样会导致组件刚刚完成更新又要再更新,进入死循环。
但是在某些特殊情况下,比如说state或者props变化触发了DOM变化,需要重新获取DOM元素宽高时然后更新某个state的时候,就不得不在这个函数里使用setState了。此时一定要给setState设置一个前提条件,以避免出现循环渲染的问题。
| 1 | componentWillUpdate(nextProps, nextState, nextContext) { | 
因此,如非必须,应该尽量避免在本函数里setState。
(5)在componentWillReceiveProps中可以setState,不会造成二次渲染。由于只有props的变化才会触发componentWillReceiveProps事件,因为在这个事件里setState不会造成不停触发组件更新的死循环,可以放心在这个函数里setState。

为什么虚拟DOM比原生DOM性能更高
React的基本思维模式是每次有变动就整个重新渲染整个应用,相当于设置 innerHTML,只不过它设置的是内存里面 Virtual-DOM,而不是真实的 DOM。
React厉害的地方并不是说它比DOM快(这句话本来就是错的),而是说不管你数据怎么变化,我都可以以最小的代价来更新DOM。方法就是在内存里面用新的数据刷新一个虚拟的DOM数,然以后新旧DOM树进行比较,找出差异,再将差异更新到真正的DOM 树上。
原生DOM性能低,因为DOM的规范迫使浏览器在实现的时候为每一个DOM元素添加了非常多的属性,然而这其中很多我们都用不到
而React对虚拟DOM的属性进行了精简,非常轻量化,并且使用了diff的算法,比较前后差异,最后只把变化的部分一次性应用到真实的DOM树
React的变动检查由于是DOM结构层面的,即使是全新的数据,只要最后渲染结果没变,那么就不需要做无用功。
更正确的说法应该是:虚拟DOM不一定比原生DOM性能高,但是让开发者更省心的更新数据。
首先, 虚拟DOM并没有比直接原生操作更快, 所谓”快”是有条件的
比如点赞, 数字+1, 直接操作DOM会更快。如果你能自己捋请规则, 每回手动操作DOM的时候, 都只改动应该改变的, 那DOM操作永远比虚拟DOM快。
但如果你的改动勾连的地方很多,而且要保持状态,那虚拟DOM的自动diff无疑会让你省更多心。
比如一个列表, 列表项有点赞等状态, 回复数量等信息, 有动态新增, 有动态加载, 这时候你要直接操作DOM会很繁琐。
虚拟DOM的核心在于diff,它自动帮你计算那些应该调整,然后只修改该修改的区域, 省下的不是运行速度这种小速度,而是开发速度/维护速度/逻辑简练程度等”总体速度”
diff算法
比较两棵DOM树的差异是Virtual DOM算法最核心的部分,这也是所谓的Virtual DOM 的diff算法。两个树的完全的diff算法是一个时间复杂度为O(n^3)的问题。但是在前端当中,你很少会跨越层级地移动DOM元素。所以Virtual DOM只会对同一个层级的元素进行对比:

上面的div只会和同一层级的div对比,第二层级的只会跟第二层级对比。这样算法复杂度就可以达到O(n)。