this.setState
是class组件一个重要的概念,
他并不在函数组件中,因为函数组件根本没有this
我们直接进入正题
为什么说setState
一定要用不可变值?
可能很小伙伴不理解,什么是不可变值?
一、什么是不可变值?
其实,不可变值可以理解为函数式编程的纯函数
也就是:在修改数值时,生成的数值不影响原来的数值
举个例子:
const a = 1
const b = a + 1
我们就可以理解,使用 a 生成 b 时, a 是不变的,
那可以理解为 a 就是不可变值,
毕竟,我们也用了 const
去定义一个常量 a
那反例就是
let a = 1
a++ // 等价于 a = a + 1
很明显,a已经改变了,那a就不是不可变值
经过,这简单的例子,我想你应该明白了什么是不可变值
那么接下来,就可以了解为什么setState一定要用不可变值了
二、为什么setState一定要用不可变值?
这里不卖关子,直接说结论:为了性能优化
用过react的小伙伴都知道
reacat在更新过程中,有shouldComponentUpdate
这么一个生命周期函数
「shouldComponentUpdate
具有"拦截更新渲染"的作用」
「shouldComponentUpdate
如果返回值是true
那就是去更新渲染,反之,不会更新渲染」
如果你觉得对上面的话很懵,那可以继续往下看
举个例子
import React from 'react'
export default class Page extends React.Component {
state = {
num: 0
}
handleClick = () => {
this.setState({
num: 0
})
}
componentDidUpdate () {
console.log('组件更新了~')
}
render () {
return (
<>
<button onClick={this.handleClick}> 点击更新num </button>
</>
)
}
}
这是一段非常简单的代码,num原来值是0,点击按钮时 num 赋值也为0
效果图:
在setState
时候,生命周期函数componentDidUpdate
触发了更新
但是,我们在下面加一段代码
import React from 'react'
export default class Page extends React.Component {
state = {
num: 0
}
handleClick = () => {
this.setState({
num: 0
})
}
/*新增代码开始*/
/**
* React生命周期函数: 是否要更新组件
* @param nextProps 更新后的属性值
* @param nextState 更新后的状态值
* @returns {boolean} 返回 true 代表更新,返回 false 代表不更新
*/
shouldComponentUpdate (nextProps, nextState) {
if (nextState.num === this.state.num) {
// nextState
console.log('nextState', nextState.num)
console.log('this.state', this.state.num)
return false // 组件不更新
}
return true // 组件更新
}
/*新增代码结束*/
componentDidUpdate () {
console.log('组件更新了~')
}
render () {
return (
<>
<button onClick={this.handleClick}>点击更新num</button>
</>
)
}
}
效果图:
现在,你就明白
「shouldComponentUpdate
具有"拦截更新渲染"的作用」 的意思了
但,你也应该明白:「react的父组件更新,子组件也会随着更新」
所以,如果父组件更新,而子组件不想更新
我们就可以用shouldComponentUpdate
去做"拦截更新渲染"
举个例子:
目录结构:
父组件:
import React from 'react'
import AComp from '../components/AComp'
export default class Page extends React.Component {
state = {
num: 0,
list: ['a', 'b', 'c']
}
handleClick = () => {
this.setState({
num: this.state.num + 1
})
}
componentDidUpdate () {
console.log('父组件更新了~')
}
render () {
return (
<>
<button onClick={this.handleClick}>点击更新num</button>
<p>{this.state.num}</p>
<AComp list={this.state.list}/>
</>
)
}
}
子组件:
import React from 'react'
import { isEqual } from 'lodash'
export default class AComp extends React.Component{
componentDidUpdate () {
console.log('子组件A更新了~')
}
shouldComponentUpdate (nextProps, nextState) {
// 引入 lodash 的 isEqual 比较数组值是否相等
if (isEqual(nextProps.list, this.props.list)) {
return false // 组件不更新
}
return true // 组件更新
}
render () {
const { list } = this.props
return(
<ul>
{ list.map((item, index) =>{
return (
<li key={index}>
{item}
</li>
)
}) }
</ul>
)
}
}
效果图:
那,说了这么多,到底和不可变值什么关系呢?
我们稍微修改下上述例子
那么有意思的就来了
父组件:
import React from 'react'
import AComp from '../components/AComp'
export default class Page extends React.Component {
state = {
num: 0,
list: ['a', 'b', 'c']
}
// 修改的代码
handleClick = () => {
this.state.num++
this.state.list.push('d')
this.setState({
num: this.state.num,
list: this.state.list
})
}
componentDidUpdate () {
console.log('父组件更新了~')
}
render () {
return (
<>
<button onClick={this.handleClick}>点击更新num</button>
<p>{this.state.num}</p>
<AComp list={this.state.list}/>
</>
)
}
}
子组件:
import React from 'react'
import { isEqual } from 'lodash'
export default class AComp extends React.Component{
componentDidUpdate () {
console.log('子组件A更新了~')
}
shouldComponentUpdate (nextProps, nextState) {
if (isEqual(nextProps.list,this.props.list)) {
// 引入 lodash 的 isEqual 比较数组值是否相等
console.log('子组件的属性值', isEqual(nextProps.list,this.props.list))
return false // 组件不更新
}
return true // 组件更新
}
render () {
const { list } = this.props
return(
<ul>
{ list.map((item, index) =>{
return (
<li key={index}>
{item}
</li>
)
}) }
</ul>
)
}
}
效果图:
这么写一看好像没有错误,但,其实有非常致命的错误
「state 的 list 已经被修改了,子组件却没有更新!」
但其实,如果你用shouldComponentUpdate
在父组件上,也不会更新
为什么呢?
this.state.list.push('d')
this.setState({
list: this.state.list
})
因为push
会改变原来的数组
也就是说this.state.list.push()
其实已经改变了this.state.list
而还进行了一次this.setState,虽然没报错,但其实是错的
那乍一看
this.state.num++ // this.state.num = this.state.num + 1
this.setState({
num: this.state.num,
})
也是错的,为什么会更新?
因为shouldComponentUpdate
默认是true
react作为很多人使用用的框架,是没办法阻止这些问题
所以,就暴露了一个钩子,让使用者自行使用
那,父组件的正确写法应该是:
import React from 'react'
import AComp from '../components/AComp'
export default class Page extends React.Component {
state = {
num: 0,
list: ['a', 'b', 'c']
}
handleClick = () => {
const arr = this.state.list.slice() // 进行一次数组复制
arr.push('d')
this.setState({
num: this.state.num + 1,
list: arr
})
}
componentDidUpdate () {
console.log('父组件更新了~')
}
render () {
return (
<>
<button onClick={this.handleClick}>点击更新num</button>
<p>{this.state.num}</p>
<AComp list={this.state.list}/>
</>
)
}
}
子组件不变
效果图:
所以说,小伙伴们应该注意的是:
①什么数组的api会改变数组,什么数组的api不会改变数组
②对象的深拷贝和浅拷贝
这也是前端面试经常考察的内容
当然,如果你觉得每次都要手写比较可以试试PureComponent
PureComponent
会在每次更新前作一次浅比较
而,如果你想彻底拥抱不可变值可以去了解一下Immutability
毕竟,深拷贝还是比较消耗性能的~
感谢阅读