参考:https://www.cnblogs.com/cazj/p/10927879.html
https://segmentfault.com/a/1190000015463599
一、React中的核心概念
1、DOM的本质: 浏览器中的概念,用js对象来表示页面上的元素,并提供操作DOM对象的API
2、react中的虚拟DOM:是框架中的概念,是程序员用js对象来模拟页面上的DOM和DOM 的嵌套
3、为什么要实现虚拟DOM:为了实现页面中DOM元素的高效更新
4、DOM和虚拟DOM的区别:
- DOM:浏览器中提供的概念,用js对象表示页面上的元素,并提供操作议元素的API
- 虚拟DOM:手动用js对象来模拟DOM元素和嵌套关系
二、脱离React实现虚拟DOM的过程
1. 原始思路
- 定义state数据
- 定义模板,即对应于React框架中render函数里返回的jsx代码部分
- 数据+模板,结合生成真实dom并显示
- state改变后
- 数据+模板,结合生成真实dom替换原始dom
缺陷:
第一次生成完整dom片段
第二次生成完整dom片段
第二次替换第一次dom
极其损耗性能,因为每次都需要重新渲染整个dom,但事实往往只有一部分dom元素改变
2. 第一次改进
- 定义state数据
- 定义模板,即对应于React框架中render函数里返回的jsx代码部分
- 数据+模板,结合生成真实dom并显示
- state改变后
- 数据+模板,结合生成真实dom,但不直接替换原始dom
- 新旧dom片段做对比,找出差异
- 只拿变化的dom替换原始dom
缺陷:
新旧dom片段做对比,找出差异,依旧会损耗性能,性能提升不明显
3. 第二次改进
- 定义state数据
- 定义模板,即对应于React框架中render函数里返回的jsx代码部分
- 数据+模板,结合生成真实dom并显示
<div>
1111
<p>2222</p>
</div>
- 生成虚拟dom,即一个js对象,用来描述真实dom(损耗性能,但相对生成新的真实dom元素还是比较节省的)
{
tagName: 'div',
attr: 1111,
children: {
tagName: 'p',
attr: 2222
}
- state变化后,假设
p标签内容变为“3333”
- 数据+模板,生成新的虚拟dom(极大提升性能)
{
tagName: 'div',
attr: 1111,
children: {
tagName: 'p',
attr: 3333
}
}
- 比较新旧dom的区别
- 直接操作dom,改变p标签中的内容
4. React底层实现
- 定义state数据
- jsx模板
- 数据+模板,结合生成生成虚拟dom
{
tagName: 'div',
attr: 1111,
children: {
tagName: 'p',
attr: 2222
}
- 用虚拟dom的结构生成真实的dom来显示
<div>
1111
<p>2222</p>
</div>
- state变化后,假设
p标签内容变为“3333”
- 数据+模板,生成新的虚拟dom(极大提升性能)
{
tagName: 'div',
attr: 1111,
children: {
tagName: 'p',
attr: 3333
}
}
- 比较新旧dom的区别
- 直接操作dom,改变p标签中的内容
因此,在render函数中,标签并不是真实标签,而是虚拟dom。整个的过程是:JSX -> React.createElement -> JS对象(虚拟dom)-> 真实dom
三、虚拟DOM优点
- 提升性能
- 使得跨端应用得以实现,如React Native框架的使用开发app
四、Diff算法
寻找原始虚拟DOM和新的虚拟DOM差异的算法
比对方法:同层比对
过程:
(1)React通过updateDepth对Virtual DOM树进行层级控制。
(2)对树分层比较,两棵树只对同一层次节点 进行比较。如果该节点不存在时,则该节点及其子节点会被完全删除,不会再进一步比较。
(3)只需遍历一次,就能完成整棵DOM树的比较。
特点:如果只有根节点相同,则会渲染所有子节点,会有性能损耗,但算法简单,比对速度快
diff算法参考链接:https://www.jianshu.com/p/3ba0822018cf
关于key值的设置------为何在组件循环渲染时要设置key值?
对于每个组件,如果设置了一个key值,就能直接通过key值来比对其value相对于原始value是否改变,提升了性能。
关于key值的设置------为何在组件循环渲染时不能用下标作为key值?
举个栗子,循环需要渲染一个list的内容成为三个组件,内容分别是:
a,b,c
用下标作为key值时,对应的key值为:
0-->a,1-->b,2-->c
当删除a后,对应的key值为:
0-->b,1-->c
因此,原始的b、c对应的key值发生了变化,key值变得不稳定,背离了根据key值进行比对的初衷(虽然有时候不会出现问题,但一旦出现问题便难以发现)
五、对 setState 的认知:
setState
不会立刻改变React组件中state的值.setState
通过触发一次组件的更新来引发重绘.- 多次
setState
函数调用产生的效果会合并。
重绘指的就是引起 React 的更新生命周期函数4个函数:
shouldComponentUpdate
(被调用时this.state没有更新;如果返回了false,生命周期被中断,虽然不调用之后的函数了,但是state仍然会被更新)componentWillUpdate
(被调用时this.state没有更新)render
(被调用时this.state得到更新)componentDidUpdate
如果每一次 setState 调用都走一圈生命周期,光是想一想也会觉得会带来性能的问题,其实这四个函数都是纯函数,性能应该还好,但是render函数返回的结果会拿去做Virtual DOM比较和更新DOM树,这个就比较费时间。
目前React会将setState的效果放在队列中,积攒着一次引发更新过程。为的就是把 Virtual DOM 和 DOM 树操作降到最小,用于提高性能。