React渲染流程
React更新流程
-
React在props或state发生改变时,会调用React的render函数,创建新的虚拟DOM树
-
React基于新旧虚拟DOM树之间的差别来决定如何更新UI
- 如果一棵树参照另一棵树进行完全比较更新,那么即使是最先进的算法,复杂度也为 O(n3) ,n是树中元素的数量
- 如果在React中使用了该算法,那么在元素数量非常多的情况下,开销是非常昂贵的,性能会变得非常低效
-
React
- 同层节点之间相互比较,不会跨节点比较
- 不同类型的节点,产生不同的树结构
- 开发过程中,可以通过key属性来指定哪些节点在不同的渲染下保持稳定
Key属性的作用
在遍历列表时,不添加key属性的情况下,会有下面这个警告:
有无key属性的几种情况:
-
在最后位置插入子元素,这种情况有没有key意义不大
-
在前面/中间插入子元素
- 这种做法在没有key的情况下,后面子元素的位置都发生了变化,React默认是按照顺序来比较元素的,所以所有的子元素都会被修改
-
当子元素拥有key时,React会比较新旧树中相同key的元素,而不是按照顺序进行比较
- 在这种情况下,相同key在位置发生变化时仅仅进行位移,不需要进行任何修改
- 将新的元素插入到指定位置即可
注意事项:
- key在同级元素中应该是唯一的,开发中一般使用数据的id
- 不要使用随机数(每次不一样)
- 使用数组的索引index,可以消除警告,但是对性能没有优化(索引可能会发生变化)
shouldComponentUpdate
调用setState函数后render函数会被调用,所有的子组件都需要重新render,进行diff算法,性能必然是很低的
- 事实上,很多组件重新执行render不是必要的
- 组件执行render函数应该有一个前提,就是在依赖的数据(state、props)发生改变时,再调用自己的render函数
React提供了一个生命周期方法shouldComponentUpdate(nextProps, nextState, nextContext)(简称SCU),该方法有两个参数,并且需要返回一个boolean类型
参数:
- 参数一:nextProps-修改之后,最新的props
- 参数二:nextState-修改之后,最新的state
- 参数三:nextContext0修改之后,最新的context。仅当你指定了
static contextType(更新的)或static contextTypes(旧版)时才可用。
返回值:
- 返回值为true,调用render函数
- 返回值为false,不调用render函数
- 默认的返回值是true,也就是只要props或者state中的数据发生了变化,就会调用render函数
import React from 'react'
export class Home {
shouldComponentUpdate(nextProps) {
if (this.props.header !== nextProps.header) {
return true
}
return false
}
render() {
console.log("Home render")
return (
<div>
<h2>标题:{this.props.header}</h2>
</div>
)
}
}
export default Home
PureComponent
如果所有的类组件,都需要手动实现 shouldComponentUpdate ,那么会增加很多的工作量
React已经考虑到了这一点,实现方法:
- 将 class 继承 PureComponent
在源码中,调用 !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState),这个shallowEqual就是进行浅层比较
高阶组件memo
在类组件中可以使用 PureComponent,那么函数组件呢?
- 在函数组件的props没有发生变化时,同样不希望重新渲染其DOM结构
需要使用一个高阶组件memo对函数进行包裹:
- 当message发生变化时,才会重新执行函数,否则不会重新执行
import { memo } from "react"
const Profile = memo(function(props) {
console.log("Home render")
return <h2>Home: {props.message}</h2>
})
// export default memo(Profile)
export default Profile
不可变数据的力量
不要直接修改状态的值,而是基于当前状态创建新的状态值
import React, { PureComponent } from 'react'
export class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
books: [
{ name: "你不知道JS", price: 99, count: 1 },
{ name: "JS高级程序设计", price: 88, count: 1 },
{ name: "React高级设计", price: 78, count: 2 },
{ name: "Vue高级设计", price: 95, count: 3 },
]
}
}
addNewBook() {
const newBook = { name: "Angular高级设计", price: 88, count: 1 }
// 错误的修改方式:直接修改原有的state, 重新设置一遍
// 在PureComponent是不能重新渲染的
// this.state.books.push(newBook)
// this.setState({ books: this.state.books })
// 正确的修改方式:赋值一份books, 在新的books中修改, 设置新的books
const books = [...this.state.books]
books.push(newBook)
this.setState({ books: books })
}
addBookCount(index) {
// this.state.books[index].count++
const books = [...this.state.books]
books[index].count++
this.setState({ books: books })
}
render() {
const { books } = this.state
return (
<div>
<h2>数据列表</h2>
<ul>
{
books.map((item, index) => {
return (
<li key={index}>
<span>name:{item.name}-price:{item.price}-count:{item.count}</span>
<button onClick={e => this.addBookCount(index)}>+1</button>
</li>
)
})
}
</ul
<button onClick={e => this.addNewBook()}>添加</button>
</div>
)
}
}
export default App