react中性能优化、 memo、 PureComponent、shouldComponentUpdate 的使用
React
是一个用于构建用户界面的JavaScript
库,主要负责将数据转换为视图,要保证数据和视图的统一,react
通过 重新render
来保证数据和视图的统一,但当数据没有变化时,视图重新渲染,就会造成不必要的性能浪费
看下面这个例子,
需求:likeList数据改变 更新子组件
// 点击每个button都会重新渲染子组件 如何解决
class Children extends React.Component {
render() {
console.log('Children render ')
return (
<div>
Children Component
<div>likes:</div>
<ul>
{this.props.classInfo.student.likeList.map((likeList, index) => (
<li key={index}>{likeList}</li>
))}
</ul>
</div>
)
}
}
export default class Parnet extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0,
classInfo: {
classNmae: '电信1班',
student: {
name: 'jack',
likeList: ['girl'],
},
},
}
}
render() {
const { classInfo, count } = this.state
return (
<div>
<button
onClick={() => {
this.setState({ count: count + 1 })
}}
>
button1
</button>
<button
onClick={() => {
this.setState({ count: count + 1 })
this.setState({ classInfo: { ...classInfo } })
}}
>
button2
</button>
<button
onClick={() => {
this.setState({ count: count + 1 })
this.setState({
classInfo: { ...classInfo, classNmae: '电信1班' + Math.random() * 10 },
})
}}
>
button3
</button>
<button
onClick={() => {
classInfo.student.likeList.push('boy' + Math.random() * 10) // 数组加入数据
this.setState({ count: count + 1 })
this.setState({
classInfo: {
...classInfo,
student: { ...classInfo.student, likeList: [...classInfo.student.likeList] },
},
})
}}
>
button3
</button>
<Children classInfo={classInfo} />
count: {count}
</div>
)
}
}
这个例子主要是用两个组件,一个 Parent
组件 和 一个 Children
组件,当我们在修改 Parent
组件的 count
时, Parent
组件应该重新渲染,保持数据 和 视图的统一,而 Children
组件 这个时候应不应该重新渲染呢,按道理,不会重新渲染,因为他本身的数据没有变,但当我们点击button1
的时候,发现 每次 count
改变, Children
组件都会重新渲染一次,这明显存在问题,而 react
提供了几种方案,供我们解决这种问题:
类组件
PureComponet
React.PureComponent
类似于 React.Component
。它们的不同之处在于React.Component
没有实现 shouldComponentUpdate()
,但是 React.PureComponent
实现了它。对属性和状态采用浅比较
的方式。适用于简单数据类型的属性传递
如果传入的值,层次比较复杂,就需要我们深层次对比,使用``shouldComponentUpdate`生命周期函数进行比较。
class Children extends React.PureComponent {
render() {
console.log('Children render ')
return (
<div>
Children Component
<div>likes:</div>
<ul>
{this.props.classInfo.student.likeList.map((likeList, index) => (
<li key={index}>{likeList}</li>
))}
</ul>
</div>
)
}
}
export default class Parnet extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0,
classInfo: {
classNmae: '电信1班',
student: {
name: 'jack',
likeList: ['girl'],
},
},
}
}
render() {
const { classInfo, count } = this.state
return (
<div>
<button
onClick={() => {
this.setState({ count: count + 1 })
}}
>
button1
</button>
<button
onClick={() => {
this.setState({ count: count + 1 })
this.setState({ classInfo: { ...classInfo } })
}}
>
button2
</button>
<button
onClick={() => {
this.setState({ count: count + 1 })
this.setState({
classInfo: { ...classInfo, classNmae: '电信1班' + Math.random() * 10 },
})
}}
>
button3
</button>
<button
onClick={() => {
classInfo.student.likeList.push('boy' + Math.random() * 10) // 数组加入数据
this.setState({ count: count + 1 })
this.setState({
classInfo: {
...classInfo,
student: { ...classInfo.student, likeList: [...classInfo.student.likeList] },
},
})
}}
>
button3
</button>
<Children classInfo={classInfo} />
count: {count}
</div>
)
}
}
运行结果
- button1点击 不会触发子组件重新渲染 因为classInfo地址值指向一样 效果已经达到 ok
发现问题
我们的需求是:父组件中likeList数据改变 更新子组件
- button2点击 【会触发子组件重新渲染,因为classInfo地址值指向不一样】,但是数据没有发生变化 【不是我们期待的结果】 如何解决?
问题
- button3点击 【会触发子组件重新渲染,因为classInfo地址值指向不一样】,【虽然里面的数据发生变化,但是子组件期待的数据没有发生变化】 【我们期待的结果是不渲染】 如何解决?
问题
- button4点击 会触发子组件重新渲染 因为classInfo地址值指向不一样 里面期待的数据有发生变化 子组件重新渲染 【是我们期待的结果】ok
如何解决上面两个问题呢?使用:shouldComponentUpdate
shouldComponentUpdate
类生命周期函数 shouldComponentUpdate
如果返回 false
,该组件就不会重新渲染,我们认为,只要 Children
组件当前的 likeList
和 下一次 更新的值地址值相等,那么 Children
组件就没必要重新渲染,shouldComponentUpdate
函数 接受两个参数,第一个是 下一次更新的 porps
值,第二个 是 下一次 更新的 state
值, 只需要当 nextProps.classInfo.student.likeList !== this.props.classInfo.student.likeList
时,return false
,Children
就不会重新渲染
class Children extends React.Component {
// 通过shouldComponentUpdate生命周期函数,自己重写方法判断是否重新渲染
shouldComponentUpdate(nextProps, nextState) {
let isTriggerRender = false // 返回 true 时,触发 re-render
// 如果likeList数据改变 re-render
if (nextProps.classInfo.student.likeList !== this.props.classInfo.student.likeList) {
isTriggerRender = true
}
console.log('isTriggerRender', isTriggerRender)
return isTriggerRender
}
render() {
console.log('Children render ')
return (
<div>
Children Component
<div>likes:</div>
<ul>
{this.props.classInfo.student.likeList.map((likeList, index) => (
<li key={index}>{likeList}</li>
))}
</ul>
</div>
)
}
}
export default class Parnet extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0,
classInfo: {
classNmae: '电信1班',
student: {
name: 'jack',
likeList: ['girl'],
},
},
}
}
render() {
const { classInfo, count } = this.state
return (
<div>
<button
onClick={() => {
this.setState({ count: count + 1 })
}}
>
button1
</button>
<button
onClick={() => {
this.setState({ count: count + 1 })
this.setState({ classInfo: { ...classInfo } })
}}
>
button2
</button>
<button
onClick={() => {
this.setState({ count: count + 1 })
this.setState({
classInfo: { ...classInfo, classNmae: '电信1班' + Math.random() * 10 },
})
}}
>
button3
</button>
<button
onClick={() => {
classInfo.student.likeList.push('boy' + Math.random() * 10) // 数组加入数据
this.setState({ count: count + 1 })
this.setState({
classInfo: {
...classInfo,
student: { ...classInfo.student, likeList: [...classInfo.student.likeList] },
},
})
}}
>
button3
</button>
<Children classInfo={classInfo} />
count: {count}
</div>
)
}
}
函数组件
memo
说了这么多,我们没有说到 memo,
但其实我们已经说完了,在使用 class
去创建 组件时,我们可以使用 PureComponent
,或者重写生命周期shouldComponentUpdate
,但当我们使用 函数组件时,我们就没办法 继承 PureComponent
,所以这个时候就用到 memo
了
①简单类型数据 解决方法:使用 React.memo(ChildrenFunc)包裹一下组件
const Children = React.memo(ChildrenFunc)
①复杂类型数据*
如何解决button2,button3问呢 解决方法 在memo的第二参数中,自己写方法判断是否让子组件渲染
function arePropsEqual(prevProps, nextProps) {
let isTriggerRender = true // 返回 true 时,不会触发 render
// 期待的数据变化,触发子组件更新
if (prevProps.classInfo.student.likeList !== nextProps.classInfo.student.likeList) {
isTriggerRender = false
}
console.log('isTriggerRender', isTriggerRender)
return isTriggerRender
}
const Children = React.memo(ChildrenFunc, arePropsEqual)
完整代码
import React, { useState } from 'react'
function ChildrenFunc(props) {
console.log('ChildrenFunc render')
return (
<div>
Children Component classInfo
<p>
classNmae--{props.classInfo.classNmae} studentName: {props.classInfo.student.name}
</p>
likes:
<ul>
{props.classInfo.student.likeList.map((likeList, index) => (
<li key={index}>{likeList}</li>
))}
</ul>
</div>
)
}
function arePropsEqual(prevProps, nextProps) {
let isTriggerRender = true // 返回 true 时,不会触发 render
// 期待的数据变化,触发子组件更新
if (prevProps.classInfo.student.likeList !== nextProps.classInfo.student.likeList) {
isTriggerRender = false
}
console.log('isTriggerRender', isTriggerRender)
return isTriggerRender
}
// 需求:likeList数据改变 更新子组件
// button每次点击 都会触发子组件重新渲染 【不是我们期待的结果】 如何解决?
// 解决方法:使用 React.memo(ChildrenFunc)包裹一下组件
// const Children = React.memo(ChildrenFunc)
// 发现问题
// button1点击 不会触发子组件重新渲染 因为classInfo地址值指向一样 ok 效果已经达到
// button2点击 会触发子组件重新渲染 因为classInfo地址值指向不一样 但是数据没有发生变化 【不是我们期待的结果】 如何解决? 问题
// button3点击 会触发子组件重新渲染 因为classInfo地址值指向不一样 虽然里面的数据发生变化,但是子组件期待的数据没有发生变化 【我们期待的结果是不渲染】 如何解决? 问题
// button4点击 会触发子组件重新渲染 因为classInfo地址值指向不一样 里面期待的数据有发生变化 会让子组件重新渲染 【是我们期待的结果】
// 如何解决button2,button3问呢
// 解决方法 在memo的第二参数中,自己写方法判断是否让子组件渲染
const Children = React.memo(ChildrenFunc, arePropsEqual)
const Parnet = () => {
const [count, setCount] = useState(0)
const [classInfo, setClassInfo] = useState(() => ({
classNmae: '电信1班',
student: {
name: 'jack',
likeList: ['girl'],
},
}))
return (
<div>
<button
onClick={() => {
setCount(pre => pre + 1)
}}
>
button1
</button>
<button
onClick={() => {
setClassInfo({ ...classInfo })
setCount(pre => pre + 1)
}}
>
button2
</button>
<button
onClick={() => {
setClassInfo({ ...classInfo, classNmae: '电信1班' + Math.random() * 10 })
setCount(pre => pre + 1)
}}
>
button3
</button>
<button
onClick={() => {
classInfo.student.likeList.push('boy' + Math.random() * 10) // 数组加入数据
setClassInfo({
...classInfo,
student: { ...classInfo.student, likeList: [...classInfo.student.likeList] }, // likeList引用发生改变
})
setCount(pre => pre + 1)
}}
>
button3
</button>
<Children classInfo={classInfo}></Children>
count: {count}
</div>
)
}
export default Parnet