0.组件更新机制
1.组件性能优化
减轻state
- 减轻state:只存储跟组件渲染相关的数据(比如:count/ 列表数据 /loading等)
- 只存储会改变的数据
- 尽量减少state的体积
- 注意:不用做渲染的数据不要放在state中
- 对于需要多个方法中用到的数据,应该放到this中
避免不必要的重新渲染shouldComponentUpdate
- 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
- 问题:子组件没有任何变化时也会重新渲染,如果避免不必要的重新渲染优化性能?
- 解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
- 在这个函数中,nextProps和nextState是最新的状态以及属性
- 作用:这个函数有返回值,如果返回true,代表需要重新渲染,如果返回false,代表不需要重新渲染
- 触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate => render)
//index.js
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
state = { count: 0 }
handleClick = () => {
// 点击时随机数,如果本次的值与上次的值不一样,则触发render
let count = Math.floor(Math.random() * 3)
this.setState({
count
})
console.log('点击被触发啦', count);
}
shouldComponentUpdate(nextProps, nextState) {
console.log('nextState', '下一次的值' + nextState.count, '上一次的值' + this.state.count);
// 如果值未变更,便不触发render,优化性能
return nextState.count !== this.state.count
}
render() {
console.log('render被触发啦');
return (
<div>
<h3>{this.state.count}</h3>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
PureComponent 纯组件
React内部会自动实现一个
shouldComponentUpdate的浅比较效果
PureComponent除了第一点之外 跟React.Component一样类组件使用 进行性能优化
React.memo高阶组件
- 是一个高阶组件 接收一个组件 返回一个增强后的组件
- 增强后的组件 当props没改变的时候 不重新渲染
- 只给函数组件使用 进行性能优化
//index.js
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
state = { count: 0 }
handleClick = () => {
// 点击时随机数,如果本次的值与上次的值不一样,则触发render
let count = Math.floor(Math.random() * 3)
this.setState({
count
})
console.log('点击被触发啦', count);
}
render() {
return (
<div>
<CounterR count={this.state.count} />
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
}
function Counter(props) {
console.log('Counter');
return <h3>随机数案例:{props.count}</h3>
}
//高阶组件
const CounterR = React.memo(Counter)
ReactDOM.render(<App />, document.getElementById('root'))
2.浏览器是如何渲染一个html文档
- 浏览器解析html文本文件
- 解析里面的html和css 生成DOM树和cssOM
- DOM树和CSS样式树 生成一棵渲染树
- 渲染树计算布局 ==>重排
- 浏览器根据渲染树进行渲染 ==> 重绘
1.重排
- 哪些操作会导致重排
- 字体大小
- 元素大小改变
- 元素位置改变
- 元素删除、新增
- 重排一定导致重绘
2. 重绘
哪些操作只导致重绘
- 颜色改变
- 元素显隐 位置大小不变 也不影响其他元素的位置大小
- 重绘不一定重排
- 重排和重绘 会导致性能问题
3.如何减少重排重绘
- 批量替换
- 局部更新 按需更新
3. jsx 转化的过程
jsx代码会通过webpack babel插件 转换成React.createElement的React.createElement转换成js对象也被称之为 虚拟DOM
4.虚拟DOM和Diff算法
虚拟DOM
本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容
- 虚拟DOM是使用js模拟真实DOM元素 而且js创建在我们事件js执行的过程中由于都处于js执行环境 所以它的开销会比较小。但是如果我们直接在执行js的时候频繁地访问真实DOM元素开销会比较大。
- 所以通过js的方式 比较新旧虚拟DOM的差异 。得到差异再转换成真实DOM的差异之后再去修改真实DOM,这样性能更高。
- 这样的修改 符合 局部更新、批量替换、按需更新的特点。效率高。
总结
- 创建js对象开销小,因此使用js对象模拟虚拟DOM
- 虚拟DOM是用来模拟真实DOM的 通过新旧虚拟DOM的差异得到真实DOM的差异
- 通过新旧真实DOM的差异来修改旧的真实DOM 效率更高。因为它导致的重排重绘最少。
- 虚拟DOM的差异不光可以转换成HTML的差异、还可以转换成小程序标签的差异、手机App组件的差异、嵌入式组件差异 。虚拟DOM最大效果可以实现跨平台开发。也就是一次学习、随处编写。
Diff算法
用于加快比较 新旧虚拟DOM差异的算法。 假如:我们有1000个虚拟DOM节点,那么要比较1000个新旧虚拟DOM之间的差异是需要比较 1000 * 1000 = 100万次。
算法原理
React 分别对
tree diff、component diff以及element diff进行了算法优化。
- tree diff 同层组件比较
- component diff 组件类型比较 类型不一致 直接替换
- element diff 数组元素 提供key 避免只是顺序不一致的重新创建
1. tree diff
分析步骤:
- 先分析顶层的父节点,如果不一样,则认为 数据全部发生了变化,执行全部替换
- 如父节点是一样的,则逐层往下进行比较,直到找到不一样的节点,则执行直接替换
对于 跨层级操作的
dom,React 没有直接把 左侧的A 拷贝到右侧的D中,而是 创建A 创建B 创建C 删除原来的A,B,c,因此,尽量避免跨层级操作dom元素。
2. component diff
对于组件之间的比较,假如 D和G是两个结构很相似的组件,但是类型不一样,那么
React也是直接视为不同的组件。执行直接替换
3. element diff
对于同一层级的元素,以上 新旧元素 除了顺序外,其他完全一样。在React中,执行算法时,发现 元素类型和KEY完全一样,那么只执行交换顺序即可,避免了反复创建和删除元素。这就是为什么我们在循环数组时要提供一个KEY的原因。
执行过程
- 初次渲染时,React会根据初始化的state(model),创建一个虚拟DOM对象(树)
- 根据虚拟DOM生成真正的DOM,渲染到页面
- 当数据变化后(setState()),会重新根据新的数据,创建新的虚拟DOM对象(树)
- 与上一次得到的虚拟DOM对象,使用Diff算法比对(找不同),得到需要更新的内容
- 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面
- 什么是虚拟dom:用js对象来表示页面上dom元素的的样式体现
- 虚拟dom的作用:高效更新页面,还有就是让react脱离了浏览器的概念
- 怎么来高效更新页面的:就是在第一次渲染页面的时候生成一份初始的虚拟dom树,然后当数据改变之后,再生成一份虚拟dom树,然后根据新旧dom树进行对比,对比完成之后,把有区别的进行更新
- diff算法的作用就是:新旧虚拟dom树在对比的时候就是通过diff算法来对比的
- diff算法又是怎么去对比的:tree diff、component diff、element diff