- React的组件化中,我们通常将组件分为容器组件、展示型组件,原则上展示型组件尽量不处理逻辑,所有的属性、事件都放在容器组件中处理
- 初始化一个留言板组件,并在
App.js
中引入使用
初始化留言板的展示.jpg
// src/components/CommentList.js
import React from 'react'
export class CommentList extends React.Component {
constructor(props) {
super(props)
this.state = {
comments: []
}
}
componentDidMount () {
// 组件加载完之后赋值数据
setTimeout(() => {
this.setState({
comments: [
{body: 'react is very good', author: 'facebook'},
{body: 'vue is very good', author: 'youyuxi'}
]
})
}, 1000)
}
render () {
return (
<div>
{/* 遍历展示留言 */}
{this.state.comments.map((c, i) => (
<Comment key={i} data={c}></Comment>
))}
</div>
)
}
}
// 留言组件,是个纯展示组件,展示留言内容及留言发布者
class Comment extends React.Component {
render () {
return (
<div>
<p>{this.props.data.body}</p>
<p>-- {this.props.data.author}</p>
</div>
)
}
}
- 在
Comment
组件打印该组件 render
的情况
class Comment extends React.Component {
render () {
// 在此处打印render的执行情况,从控制台能看出这里执行了4次render
// 首先因为严格模式,每次被触发执行render都会被执行2次,所以这里触发2次render的执行,分别是首次加载、setTimeout中的setState
console.log('Comment render')
return (
<div>
<p>{this.props.data.body}</p>
<p>-- {this.props.data.author}</p>
</div>
)
}
}
- 从上面执行
render
的情况可以看出,每次父组件数据更新都会触发 Comment
的 render
,这样就存在不合理的情况,假设如下:
// 假设目前的留言板需要做轮询更新,实时更新留言板的留言数据
// 那么每一次轮询都会触发Comment的render, Comment每一次都要做DOM对比
// 然后当留言板数据更新频率不高时,也就是有时候留言数据并没有更新,但是每一次轮询却都会触发Comment组件的render
componentDidMount () {
setInterval(() => {
this.setState({
comments: [
{body: 'react is very good', author: 'facebook'},
{body: 'vue is very good', author: 'youyuxi'}
]
})
}, 1000)
}
- 所以下一步需要解决组件频繁更新的问题,组件频繁的更新、每一次更新所做的DOM对比,对性能都是一种消耗
// 避免组件频繁更新1 - shouldComponentUpdate
// 该方法较为繁琐,也不太可能在代码里对比状态的每一个属性
class Comment extends React.Component {
shouldComponentUpdate (nextProps) {
// nextProps是接下来的新状态,被改变之后的状态
// 在shouldComponentUpdate生命周期内对比改变之后的状态与之前额状态是否一致
if(nextProps.data.body === this.props.data.body &&
nextProps.data.author === this.props.data.author) {
return false;
}
return true;
}
render () {
// 打印4次
console.log('Comment render')
return (
<div>
<p>{this.props.data.body}</p>
<p>-- {this.props.data.author}</p>
</div>
)
}
}
// 避免组件频繁更新2 - PureComponent
// 更换继承类为PureComponent,PureComponent的原理是内置shouldComponentUpdate并对shouldComponentUpdate做了一定扩展
class Comment extends React.PureComponent {
render () {
// 每秒打印2次
console.log('Comment render')
return (
<div>
<p>{this.props.data.body}</p>
<p>-- {this.props.data.author}</p>
</div>
)
}
}
// 写到这里会发现,控制台并没有停止打印 “Comment render”,依然保持每一秒打印一次
// 这是因为PureComponent只做浅比较,不会递归比较属性,在对比对象的时候只会比较对象的引用,或者只比较最外层属性
// 我们这里的每一次 setState 都是对引用的更新,所以每一次对比上一次状态,永远都是新的数据,因此Comment组件会一直render
// 所以在使用PureComponent时尽量避免对象类型数据,尽可能只用在值类型数据,或者使用一层属性的对象
// 避免组件频繁更新2 - 优化 - PureComponent
// 首先优化父组件传值的方式,将原本传的对象c扩展开
render () {
return (
<div>
{this.state.comments.map((c, i) => (
// 这里扩展相当于: <Comment key={i} body={c.body} author={c.author}></Comment>
<Comment key={i} {...c}></Comment>
))}
</div>
)
}
// 然后优化子组件获取属性的写法
class Comment extends React.PureComponent {
render () {
// 打印4次
console.log('Comment render')
return (
<div>
<p>{this.props.body}</p>
<p>-- {this.props.author}</p>
</div>
)
}
}
// 避免组件频繁更新3 - React.memo v16.6.0后的版本才有
// memo是一个高阶组件,是一个函数,接收一个组件返回一个新组件
// 类似PureComponent只做浅比较,解决了函数型组件没有PureComponent功能的问题
const Comment = React.memo(function (props) {
// 打印2次
console.log('Comment render')
return (
<div>
<p>{props.body}</p>
<p>-- {props.author}</p>
</div>
)
})
网友评论