我正在参加「掘金·启航计划」
一、React中为什么使用setState
在使用React时,并不能通过直接修改state而让React重新渲染界面,必须通过setState才行:
- 因为如果直接修改state中的内容,React是无法监听到其中的数据发生了改变
- 这是由于React并没有实现类似于Vue2中的Object.defineProperty 或 Vue3中的Proxy的方式来对数据进行劫持、从而监听到数据的变化
二、使用setState的三种方式
- 基本使用方式
this.setState({
message: "Hello React"
})
- 传入一个回调函数
该回调函数会将之前的state与props传递进来
使用该方式的好处为:
可以在回调函数中编写处理state的逻辑
可以获取到之前的state与props
this.setState((state, props) => {
// 1.编写一些对新的state处理逻辑
// 2.可以获取之前的state和props值
console.log(this.state.message, this.props)
return {
message: "Hello React"
}
})
- 如果期望修改数据后来处理一些逻辑,此时可采用第三种方式:
传入需要更新的内容及回调函数
该回调函数会数据更新后立即被执行
this.setState({ message: "Hello React" }, () => {
console.log("修改后的数据:", this.state.message) // 修改后的数据: Hello React
})
三、setState是同步的开始异步的
setState是异步的
import React, { Component } from 'react'
export class App extends Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
increment() {
console.log("increment...")
this.setState({
counter: this.state.counter + 1
})
console.log('counter...', this.state.counter)
}
render() {
const { counter } = this.state
console.log("render...")
return (
<div>
<h2>当前计数: {counter}</h2>
<button onClick={e => this.increment()}> + 1</button>
</div>
)
}
}
export default App
在使用setState后,输出的counter仍是执行+1之前的数据,可见setState是异步的
为什么setState要设计为异步的
setState之所以要设计为异步的主要原因有两个:
原因1
setState设计为异步的可以显著提升性能
- 如果每次调用setState都进行一次更新那么就意味着render函数会被频繁调用,界面会被频繁的重新渲染,这样效率是很低的
- React采用的方法是获取到多个更新,然后进行批量更新(setState将会对组件state的更改放入一个队列中,并通知React需要使用更新后的state重新渲染此组件及子组件。React将setState视为请求而不是立即更新组件的命令,为了更好的提高性能,React会延迟调用它,然后通过一次传递来更新多个组件)
比如,在多次通过setState来修改counter时,render函数只会重新渲染一次
import React, { Component } from 'react'
function Hello(props) {
return <h2>{props.message}</h2>
}
export class App extends Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
increment() {
console.log("increment...")
this.setState({
counter: this.state.counter + 1
})
this.setState({
counter: this.state.counter + 1
})
this.setState({
counter: this.state.counter + 1
})
}
render() {
const { counter } = this.state
console.log("render...")
return (
<div>
<h2>当前计数: {counter}</h2>
<button onClick={e => this.increment()}> + 1</button>
</div>
)
}
}
export default App
需要注意的是,这里虽然使用了三次setState,但是在对counter进行更新的时候设置进去的this.state.counter此时为0,所以三次setState均相当于this.setState({ counter: 0 + 1 })
如果想让结果变为3,可写为:
increment() {
this.setState((state) => {
return {
counter: state.counter + 1
}
})
this.setState((state) => {
return {
counter: state.counter + 1
}
})
this.setState((state) => {
return {
counter: state.counter + 1
}
})
}
原因2
如果同步更新了state,由于还未执行到render函数,那么state和props不能保持同步
state与props不同步的话会导致很多bug,也不方便问题的排查
四、如何获取到setState后的异步结果
如果想获取到setState后的结果,有两种方式:
方式1
通过setState的回调函数来获取
- setState可接收两个参数,第二个参数为回调函数,该回调函数会在state更新后立即被执行
this.setState({ message: "Hello React" }, () => {
console.log("修改后的数据:", this.state.message) // 修改后的数据: Hello React
})
方式2
在生命周期函数 componentDidUpdate 中获取更新后的数据
componentDidUpdate() {
console.log(this.state.message)
}
五、setState一定是异步的吗
React18之前,setState是否为异步分为两种情况:
- 在组件生命周期或React合成事件中,setState是异步的
- 在setTimeout或者原生dom事件中,setState是同步的
而React18后,setState默认是异步的
在React18后,如果希望同步拿到setState后的结果,需要执行flushSync操作,但这可能会损害性能,慎用!
import React, { Component } from 'react'
import { flushSync } from 'react-dom'
export class App extends Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
increment() {
flushSync(() => {
this.setState({
counter: this.state.counter + 1
})
})
console.log('counter....', this.state.counter)
}
render() {
const { counter } = this.state
return (
<div>
<h2>当前计数: {counter}</h2>
<button onClick={e => this.increment()}> + 1</button>
</div>
)
}
}
export default App