携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情
1. 分析
之前说的组件更新机制是针对多个组件之间的更新思想,那么对于某一个组件,我想具体分析一下其内部是怎么实现更新的。
其实 React 更新视图的思想是只要 state 发生变化就会重新渲染视图。
那么可想而知这种更新思想是存在一定问题的,那就是如果组件中只有一个 DOM 元素需要更新时,是不是也得把整个组件的内容重新渲染到页面中?
显然不是的,我们的理想状态当然是如果有变化的地方那就更新,没有变化的地方就不更新,也就是实现部分更新的思想,这样对于组件的性能也会有提高。
所以对于这一点,其实 React 是做的挺好的,因为 React 实际上就是运用虚拟 DOM 配合 Diff 算法来实现部分更新的。
总结如下几点:
- React 更新视图的思想是:只要 state 变化就重新渲染视图。
- 存在的问题:如果组件中只有一个 DOM 元素需要更新时,是不是也得把整个组件的内容重新渲染到页面中?
- 理想状态:局部更新,只更新变化的地方。
- 问题:React 是如何做到局部更新的呢?
- 回答问题:通过虚拟 DOM 配合 Diff 算法实现组件的局部更新。
2. 虚拟 DOM
虚拟 DOM(其实就是之前说的 React 元素):本质上是一个 JS 对象,用来描述在屏幕上能看到的内容,也就是 HTML 结构。换句话说,虚拟 DOM 就是在描述页面上的 HTML 结构。它们之间的关系如下图所示:
3. Diff 算法
3.1. 什么是 Diff 算法
Diff 算法是虚拟 DOM 中采用的算法,该算法会进行虚拟 DOM 对比,并返回一个用来存储两个 DOM 不同地方的、可 Patch(打补丁的意思) 的对象,最后去局部更新 DOM。
Diff 算法会把 DOM 树形结构按照层级分解,并且由上至下地进行同层比对,如果上层已经不同了,那么下面的 DOM 全部重新渲染。这样的好处是算法简单,减少比对次数,加快算法完成速度。
3.2. Diff 算法的特点
- 同层比较,不同层级的节点只有创建和删除操作。
- 比较过程中,循环从两边向中间比较。
3.3. Diff 算法的执行过程
- 初次渲染时,React 会根据初始 state(Model),创建一个虚拟 DOM 对象(树)。
- 根据虚拟 DOM 生成真正的 DOM,渲染到页面中。
初次渲染时:
- 当数据变化后(调用 setState() 后),重新根据新的数据,创建新的虚拟 DOM 对象(树)。
- 与上一次得到的虚拟 DOM 对象,使用 Diff 算法进行对比,得到需要更新的内容。
- 最终,React 只将变化的内容更新(patch)到 DOM 中,然后重新渲染到页面。
数据变化后:
4. 代码演示
- 组件 render() 调用后,根据状态和 JSX 结构生成虚拟 DOM 对象
如下列代码:
// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'
// App组件
class App extends React.PureComponent {
state = {
number: 0
}
handleClick = () => {
this.setState((state) => ({
number: state.number + 1
}))
}
render() {
// element 就是 JSX 结构,this.state.people.age就是状态
const element = (
<div className='app' >
<p>当前数值:{this.state.number}</p>
<button onClick={this.handleClick}>+1</button>
</div>
)
console.log(element)
return element
}
}
ReactDOM.render(<App />, document.getElementById('root'))
其演示如下:
可以看见,当每次点击“+1”按钮时,只有 p 标签中的值会闪一下并且进行了更新,而其他内容并没有闪烁以及更新,这就是所谓的局部更新。
如此情形是因为每当状态发生改变后,组件的 render() 方法就会被重新调用,但是每次 render() 方法重新调用都会去执行 Diff 算法的过程。
而 Diff 算法只是找出当前虚拟 DOM 与上一次虚拟 DOM 不同的地方,然后将不同的地方进行渲染,而不是渲染整个 DOM。
这说明 render() 方法调用并不意味着浏览器中的重新渲染,仅仅说明要进行 Diff。