1. React 更新机制
1.1 渲染流程
1.2 更新流程
-
React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树
-
React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI:
- 如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的复杂程度为 O(n³),其中 n 是树中元素的数量
- 如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围
- 这个开销太过昂贵了,React的更新性能会变得非常低效
-
于是,React对这个算法进行了优化,将其优化成了O(n),如何优化的呢?
- 同层节点之间相互比较,不会跨节点比较
- 不同类型的节点,产生不同的树结构
- 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定
2. keys的优化
-
遍历列表的时候,添加key属性可以起到优化效果
-
在最后位置插入数据:这种情况,有无key意义并不大
-
在前面插入数据:这种做法,在没有key的情况下,所有的li都需要进行修改
-
当子元素(这里的li)拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素,可以复用
-
-
key的注意事项:
- key应该是唯一的
- key不要使用随机数(随机数在下一次render时,会重新生成一个数字)
- 使用index作为key,对性能是没有优化的
3. render函数的优化
React 每次调用 setState 函数时都会重新执行render函数,如果当前组件含有子组件,那么子组件的render函数也会执行;如果调用 setState 时数据并未发生更新,但是render函数会执行,无疑又是性能浪费
3.1 shouldComponentUpdate(SCU)
-
修改数据调用 setState ,所有的组件都需要重新render,进行diff算法,性能必然是很低的:
- 事实上,很多的组件没有必须要重新render
- 它们调用render应该有一个前提,就是依赖的数据(state、props)发生改变时,再调用自己的render方法
-
SCU
-
SCU优化就是 一种巧妙的技术,用来减少DOM操作次数,具体为当React元素没有更新时,不会去调用render()方法
-
通过
shouldComponentUpdate
来判断props/state
中的值是否改变,再决定是否调用render函数
-
-
shouldComponentUpdate
-
该方法有两个参数:
- nextProps:修改之后,最新的props属性
- nextState:修改之后,最新的state属性
shouldComponentUpdate(nextProps, nextState) { if(this.state.message !== nextState.message || this.state.counter !== nextState.counter) { return true } return false }
-
该方法返回值是一个boolean类型:
- 返回值为true,那么就需要调用render方法
- 返回值为false,那么就不需要调用render方法
- 默认返回的是true,也就是只要state发生改变,就会调用render方法
-
3.2. PureComponent/memo
- 如果所有的类,都需要手动来实现 shouldComponentUpdate,那么会给我们开发者增加非常多的工作量
- 事实上React已经考虑到了这一点,所以React已经默认帮我们实现好了,只需要将class继承自PureComponent
-
PureComponent: 类组件
-
memo: 函数组件
- 函数式组件我们在props没有改变时,也是不希望其重新渲染其DOM树结构,使用 memo 对其进行包裹
import { memo } from "react"; export default memo(function(props) { console.log('Profile render'); return <h2>Profile: {props.message}</h2> })
4. state的不可变力量
- 修改state中的某一个数据(引用类型)
- 先对数据进行拷贝操作
- 修改拷贝之后对象, 设置新对象
- 注意: 值类型,在修改的时候,本身就全部替换掉了,所以不需要其他操作,直接改就可以
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
constructor() {
super()
this.state = {
books: [
{ name: '你不知道的JavaScript', price: 98, count: 1 },
{ name: 'JavaScript高级程序设计', price: 128, count: 2 },
{ name: 'Vue3指南', price: 88, count: 2 },
{ name: 'React实战教程', price: 88, count: 3 }
]
}
}
addBookCount(index) {
const books = [...this.state.books]
books[index].count ++
this.setState({ books: books })
}
addNewBook() {
const newBook = { name: "设计模式之美", price: 119, count: 2 }
// 1.直接修改原有的state, 重新设置一遍
// 在PureComponent是不能引入重新渲染(re-render)
// this.state.books.push(newBook)
// this.setState({ books: this.state.books })
// 2.赋值一份books, 在新的books中修改, 设置新的books
const books = [...this.state.books]
books.push(newBook)
this.setState({ books: books })
}
render() {
const {books} = this.state
return (
<div>
<h2>书籍列表</h2>
<ul>
{
books.map((book, index) => {
return (
<li key={index}>
<span>name:{book.name}-price:{book.price}-count:{book.count}</span>
<button onClick={() => this.addBookCount(index)}>+1</button>
</li>
)
})
}
</ul>
<div>
<h2>添加新书籍</h2>
<button onClick={() => this.addNewBook()}>添加书籍</button>
</div>
</div>
)
}
}
-
注意:
PureComponent
仅仅会对新老this.state.books
的值进行简单的对比。由于代码中addNewBook
方法(第一种写法)改变了同一个books
数组,使得新老this.state.books
比较的其实还是同一个数组。即便实际上数组已经变了,但是比较结果是相同的。 -
PureComponent
内部是 通过 shallowEqual 进行浅层比较- 先判断是否是同一个state/props,比较两者的属性的长度是否相等
- 再取出内部的某一项(引用类型)进行浅层比较,看是否是同一个对象