1、前置信息
由于页面重排与重绘的问题,浏览器处理DOM比较
慢,如果页面比较复杂,频繁的操做DOM
会造成很大的开销
。
传统的Web应用中
,数据的变化会实时地更新到用户界面中,于是每次数据微小的变化都会引起DOM
的渲染。
而虚拟DOM是将所有
的操作聚集到一块,计算出所有的变化后,统一更新
一次虚拟DOM
一个页面如果有100次变化,真实DOM的就会渲染100次,而虚拟DOM只需要渲染一次,从这点上来看,页面越复杂,虚拟DOM的优势越大
tips:
React
的核心是JSX
语法,JSX
就是JS
上的扩展,是一个拥有javascript全部功能的模板语言。通过Babel
去对JSX
进行转化为对应的JS对象,最终展示到浏览器- react组件首字母大写:
Babel
去对JSX
进行转化时,判断是原生DOM标签
,还是React组件
,就是根据标签的首字母,小写为原生标签,大写是React组件 - React.Fragment:在
React
中,组件是不允许返回多个节点的,等价于<></>,实际上是没有节点的,创建虚拟DOM时:React.createElement(React.Fragment, null, ""),实际上
<React.Fragment>
允许有key
的,而<></>
无法附上key
2、什么是虚拟DOM
虚拟DOM
实际上就是对象
<div className='Index'><div>哈喽</div><ul><li>React</li><li>Vue</li></ul></div> // 转化为虚拟DOM{type: 'div',props: { class: 'Index' },children: [{type: 'div',children: '夏目'},{type: 'ul',children: [{type: 'li',children: 'React'},{type: 'li',children: 'Vue'},]}]}// type:实际的标签 // props:标签内部的属性(除key和ref,会形成单独的key名) // children: 为节点内容,依次循环
// 从结构上来说,虚拟DOM
并没有真实DOM上绑定的很多属性和方法
,因此,就算直接把虚拟DOM
删除后,重新建一个也是非常快的
// 可以通过console对比查看
const
VDOM = React.createElement(
'div'
, {},
'虚拟'
)
const
DOM = document.createElement(
"div"
);
DOM.innerHTML =
'真实'
console.log(`虚拟DOM:`, VDOM)
console.log(`真实DOM:`, DOM)
3、虚拟DOM的优点(浏览器在处理DOM的时候会很慢,处理JavaScript会很快)
效率:React
会通过虚拟DOM
来确保DOM
的匹配,如何操作DOM
,更新DOM
,React会处理好,只需要关注业务逻辑
性能:
1、React
会将整个DOM
保存为虚拟DOM
,如果有更新,会维护两个虚拟DOM,来比较之前的状态
和当前的状态
,并会确定哪些状态被修改,然后将这些变化更新到实际DOM上
,一旦真正的DOM发生改变,也会更新UI
2、所以在虚拟DOM
发生变化的时候,只会更新局部,而非整体。同时,虚拟DOM
减少了非常多的DOM操作
,所以性能会提升优势:
1、优势是在于diff算法
和批量处理策略
,将所有的DOM操作搜集起来,一次性去改变真实的DOM
2、但在首次渲染上,虚拟DOM
会多了一层计算,消耗一些性能,所以有可能会比html
渲染的要慢
3、虚拟DOM
实际上是找了一条最短,最近的路径,并不是说比DOM操作的更快,而是路径最简单
4、兼容性
React
基于虚拟DOM
实现了一套自己的事件机制,并且模拟了事件冒泡和捕获的过程,采取事件代理、批量更新等方法,满足各个浏览器的事件兼容性问题- 对于跨平台,
React
和React Native
都是根据虚拟DOM画出相应平台的UI
层,只不过不同的平台画法不同
5、如何实现虚拟DOM
JSX
代码会被转为React.createElement
的形式- React.createElement:它的功能是将
props
和子元素
进行处理后返回一个ReactElement
对象(key
和ref
会特殊处理)
ReactElement
这个对象会将传入的几个属性进行组合并返回- type:实际的标签
- props:标签内部的属性(除
key
和ref
,会形成单独的key
名) - children: 为节点内容,依次循环
- type:实际的标签,原生的标签(如'div'),自定义组件(类或是函数式)
- props:标签内部的属性(除
key
和ref
,会形成单独的key
名) - key:组件内的唯一标识,用于
Diff
算法 - ref:用于访问原生
dom
节点 - owner:当前正在构建的
Component
所属的Component
- ?typeof:默认为
REACT_ELEMENT_TYPE
,可以防止XXS
-
转化为真实DOM
虚拟DOM
转化为真实DOM
的过程大体上可以分为四步:处理参数
、批量处理
、生成html、
渲染html
- 处理参数:当我们处理好组件后,我们需要
ReactDOM.render(element, container[, callback])
将组件进行渲染,这里会判断是原生标签还是React自定义组件 - 批量处理:这个过程就会统一进行处理,具体的执行机制,之后会单独写篇文章讲解
- 生成html:对特殊的
DOM
标签、props
进行处理,并根据对应的标签类型创造对应的DOM
节点,利用updateDOMProperties
将props
插入到DOM
节点,最后渲染到上面 - 渲染html:渲染html节点,渲染文本节点,但不同的浏览器可能会做不同的处理
- 处理参数:当我们处理好组件后,我们需要
6、React更新虚拟DOM(diff算法)
1、准备
React
会维护两个虚拟DOM,通过对比进行更新,最终渲染到真实DOM中。
diff算法并不是react中提出来的,而是引用并且结合react进行优化,最终形成了react的diff算法。
举个栗子:
- 在计算一颗树转化为另一颗树有哪些改变时,
传统的diff算法
通过循环递归对节点进行依此对比,算法复杂度达到了O(n^ 3),也就是说,如果展示 一千个节点,就要计算十亿次 React
的diff
算法,算法复杂度为O(n),如果展示一千个节点,就要计算一千次。可以看出优化成功非常显著
2、react diff算法
React通过三步走进行优化:
- tree diff: DOM 节点跨层级的移动操作特别少,可以忽略不计。所以只进行同级的比较
React
通过updateDepth
对Virtual DOM 树
进行层级控制,也就是同一层,在对比的过程中,如果发现节点不在了,会完全删除
不会对其他地方进行比较,这样只需要对树遍历一次
就OK了 - component diff:拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。相同类型的组件和不同类型的组件
- 对同种类型组件对比,按照层级比较继续比较虚拟DOM树即可,但有种特殊的情况,当组件A如果变化为组件B的时候,有可能虚拟DOM并没有任何变化,所以用户可以通过shouldComponentUpdate() 来判断是否需要更新,判断是否计算
- 对于不同组件来说,
React
会直接判定该组件为dirty component(脏组件),无论结构是否相似,只要判断为脏组件就会直接替换整个组件的所有节点
- element diff:对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
1、节点比较,对于同一层级的一子自节点,通过唯一的key进行比较
2、当所有节点处以同一层级时,React
提供了三种节点操作:插入
、移动
、删除
3、循环中的key
- 不能使用index: 节点进行插入、移动、删除操作时,索引发生变化,不能正确识别节点
- 不能使用index+str: 同上,导致所有节点不能复用
- 使用唯一值做key,diff算法的关键点
4、fiber
时间切片,可以将渲染工作进行切片,提高渲染效率。
- 从运行机制上来解释,
fiber
是一种流程让出机制,它能让react
中的同步渲染进行中断,并将渲染的控制权让回浏览器,从而达到不阻塞浏览器渲染的目的。 - 从数据角度来解释,
fiber
能细化成一种数据结构,或者一个执行单元。
Fiber算法的核心概念
-
任务中断和优先级:Fiber算法利用浏览器的空闲时间执行任务,如果遇到更高优先级的任务,当前任务可以被中断,优先执行高级别任务。这种设计允许React在处理复杂界面时更加灵活,不会因为长时间占用主线程而导致页面卡顿。
-
任务拆分:Fiber将任务拆分成一个个的小任务,每个任务都可以独立执行。这种拆分使得任务可以更容易地被中断和重新开始,提高了应用的响应性。
-
Fiber对象:每个Fiber对象包含节点类型、属性、DOM对象、操作标记等信息。Fiber对象之间通过父子关系连接,形成一个树状结构。这种结构使得React可以高效地管理和更新DOM。
Fiber算法的实现细节
-
构建和提交:Fiber算法将DOM比对算法分成两部分:构建(可中断)和提交(不可中断)。构建阶段负责创建和更新Fiber对象,而提交阶段则负责将更新应用到实际的DOM上。
-
虚拟DOM树的遍历:React使用虚拟DOM树来比较前后两次渲染的结果,找出差异并进行更新。Fiber算法通过遍历虚拟DOM树,使用高效的比对算法来减少不必要的DOM操作,从而提高性能。
-
生命周期方法:在Fiber算法中,组件的生命周期方法被分为三个阶段:pre-mutation(变更前)、mutation(变更中)和post-mutation(变更后)。这种划分使得React可以更精细地控制组件的更新过程,进一步提高性能。
通过这些机制,Fiber算法使得React能够更高效地处理复杂的用户界面,提高应用的响应性和性能
tips:
主流的显示器刷新率其实都是60帧/S,也就是一秒画面会高速的刷新60次,按照1S=
1000ms
,那么一帧的预算时间其实是1000ms/60帧
也就是16.66ms
。
- 背景
React
是函数式编程,单向数据流,需要手动setState
来更新,所以当数据改变时会默认全部重新渲染整个组件树,而构建虚拟DOM
采用的同步递归的方式,如果组件很复杂且嵌套深,那么这个构建虚拟DOM
的过程就需要很多时间,而这种任务默认要执行完才会把控制权交给浏览器,一旦执行时间很长,可能就会地把浏览器卡死。
fiber
是可以使渲染过程被中断,可以把控制权先交还给浏览器,让位高优先级的任务,等浏览器空闲时再恢复渲染,这样就不会显得卡顿,而是一帧一帧有规律的执行任务,看起来虽然有点慢,但是一直在执行的.
每一个被创建的虚拟dom
都会被包装成一个fiber
节点 -
特点
- 支持增量渲染,
fiber
将react
中的渲染任务拆分到每一帧。(不是一口气全部渲染完,走走停停,有时间就继续渲染,没时间就先暂停) - 支持暂停,终止以及恢复之前的渲染任务。(没渲染时间了就将控制权让回浏览器)
- 通过
fiber
赋予了不同任务的优先级。(让优先级高的运行,比如事件交互响应,页面渲染等,像网络请求之类的往后排) - 支持并发处理(结合第3点理解,面对可变的一堆任务,
react
始终处理最高优先级,灵活调整处理顺序,保证重要的任务都会在允许的最快时间内响应,而不是完全按顺序来)
- 支持增量渲染,