This. SetState is an important concept for class components,
It’s not in the function component, because the function component doesn’t have this at all
Let’s get straight to the point
Why does setState have to be immutable?
You may not understand, what is immutable value?
What is immutable value?
In fact, immutable values can be understood as pure functions of functional programming
When you modify a value, the generated value does not affect the original value
Here’s an example:
const a = 1
const b = a + 1
Copy the code
So we can understand that when WE use a to form B, a doesn’t change,
That can be interpreted as a being immutable,
After all, we also use const to define a constant a
The counter example is
let a = 1
A ++ // is the same thing as a = a+ 1
Copy the code
Obviously, a has changed, so a is not immutable
After this simple example, I think you get the idea what is immutable value
So now, why does setState have to be immutable
Why setState must use immutable values?
Not to keep you in suspense, but to get straight to the conclusion: for performance optimization
Anyone who has used react knows this
The reacat update process has a shouldComponentUpdate lifecycle function
“shouldComponentUpdate
Has “intercept update render” function“
“shouldComponentUpdate
If the return value istrue
That is to update the render, otherwise, do not update the render“
If you’re confused by the above, read on
For example
import React from 'react'
export default class Page extends React.Component {
state = {
num: 0
}
handleClick = () => {
this.setState({
num: 0
})
}
componentDidUpdate () {
console.log('Component updated ~')
}
render () {
return (
<>
<button onClick={this.handleClick}>
</>
)
}
}
Copy the code
This is a very simple piece of code. The original value of num is 0, and the value of num is also 0 when the button is clicked
Effect:
At setState, the lifecycle function componentDidUpdate triggers the update
However, let’s add a bit of code below
import React from 'react'
export default class Page extends React.Component {
state = {
num: 0
}
handleClick = () => {
this.setState({
num: 0
})
}
/* Start adding code */
/ * *
* React lifecycle function: Whether to update components
* @param nextProps Updated property value
* @param nextState Updated status value
* @returns {Boolean} ReturnstrueFor update, returnfalseDoes not update
* /
shouldComponentUpdate (nextProps, nextState) {
if (nextState.num === this.state.num) {
// nextState
console.log('nextState', nextState.num)
console.log('this.state', this.state.num)
return false// The component is not updated
}
return true// Component update
}
/* End of new code */
componentDidUpdate () {
console.log('Component updated ~')
}
render () {
return (
<>
<button onClick={this.handleClick}>
</>
)
}
}
Copy the code
Effect:
Now you know what “shouldComponentUpdate” means to “intercept updated renders”
However, you should also understand: “React’s parent updates, and its children update with it.”
So, if the parent component updates and the child component doesn’t want to update
We can use shouldComponentUpdate to intercept update render
Here’s an example:
Directory structure:
The parent component:
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('Parent component updated ~')
}
render () {
return (
<>
<button onClick={this.handleClick}>
<p>{this.state.num}</p>
<AComp list={this.state.list}/>
</>
)
}
}
Copy the code
Child components:
import React from 'react'
import { isEqual } from 'lodash'
export default class AComp extends React.Component{
componentDidUpdate () {
console.log('Child component A updated ~')
}
shouldComponentUpdate (nextProps, nextState) {
// Introduce lodash's isEqual to compare array values for equality
if (isEqual(nextProps.list, this.props.list)) {
return false// The component is not updated
}
return true// Component update
}
render () {
const { list } = this.props
return(
<ul>
{ list.map((item, index) =>{
return (
<li key={index}>
{item}
</li>
)
})}
</ul>
)
}
}
Copy the code
Effect:
So, having said all this, what does it have to do with immutable values?
Let’s modify the example a little bit
So here’s where the fun comes in
The parent component:
import React from 'react'
import AComp from '.. /components/AComp'
export default class Page extends React.Component {
state = {
num: 0,
list: ['a'.'b'.'c']
}
// Modify the code
handleClick = () => {
this.state.num++
this.state.list.push('d')
this.setState({
num: this.state.num,
list: this.state.list
})
}
componentDidUpdate () {
console.log('Parent component updated ~')
}
render () {
return (
<>
<button onClick={this.handleClick}>
<p>{this.state.num}</p>
<AComp list={this.state.list}/>
</>
)
}
}
Copy the code
Child components:
import React from 'react'
import { isEqual } from 'lodash'
export default class AComp extends React.Component{
componentDidUpdate () {
console.log('Child component A updated ~')
}
shouldComponentUpdate (nextProps, nextState) {
if (isEqual(nextProps.list,this.props.list)) {
// Introduce lodash's isEqual to compare array values for equality
console.log('Property values of child components', isEqual(nextProps.list,this.props.list))
return false// The component is not updated
}
return true// Component update
}
render () {
const { list } = this.props
return(
<ul>
{ list.map((item, index) =>{
return (
<li key={index}>
{item}
</li>
)
})}
</ul>
)
}
}
Copy the code
Effect:
It looks like there’s nothing wrong with this, but there’s something very wrong with it
“The list of state has been modified, but the child components have not been updated!“
But actually, if you use shouldComponentUpdate on the parent component, it doesn’t update either
Why is that?
this.state.list.push('d')
this.setState({
list: this.state.list
})
Copy the code
Because push is going to change the array
This.state.list.push () has changed this.state.list
This. SetState is called, but it is not an error
At first glance
this.state.num++ // this.state.num = this.state.num + 1
this.setState({
num: this.state.num,
})
Copy the code
Also wrong, why is it updated?
Because shouldComponentUpdate defaults to true
React, the framework that many people use, does not prevent these problems
So, a hook is exposed and left to the user to use
The correct way to write the parent component is:
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() // Make an array copy
arr.push('d')
this.setState({
num: this.state.num + 1,
list: arr
})
}
componentDidUpdate () {
console.log('Parent component updated ~')
}
render () {
return (
<>
<button onClick={this.handleClick}>
<p>{this.state.num}</p>
<AComp list={this.state.list}/>
</>
)
}
}
Copy the code
Child component invariant
Effect:
So here’s what you should pay attention to:
What array API changes an array, and what array API does not change an array
② Deep and shallow copies of objects
This is what front-end interviews often look at
Of course, try PureComponent if you feel like writing it every time
PureComponent makes a shallow comparison before each update
And if you want to embrace immutable values for good, check out Immutability
After all, deep copy is performance-intensive
Thank you for reading