在讨论这个问题之前,先看一个问题
setState执行后打印state是更新前的还是更新后的?
合成事件中
- 更新之前的值
class App extends React.Component {
state = { count: 0 }
handleClick = () => {
this.setState({ count: this.state.count + 1 })
console.info(this.state); // 是更新之前的值
}
render() {
return (
<Button onClick={this.handleClick}>点击</Button>
)
}
}
钩子函数中
- 更新之前的值
class App extends React.Component {
state = { count: 0 }
componentDidMount() {
this.setState({ count: this.state.count + 1 })
console.info(this.state); // 是更新之前的值
}
render() {
return null
}
}
原生事件中
- 更新之后的值
class App extends React.Component {
state = { count: 0 }
handleClick = () => {
this.setState({ count: this.state.count + 1 })
console.info(this.state); // 是更新之后的值
}
componentDidMount() {
document.body.addEventerListener("click", this.handleCLick)
}
render() {
return <div>...</div>
}
}
setTimeout中
- 更新之后的值
class App extends React.Component {
state = { count: 0 }
handleClick = () => {
setTimeout(() =>{
this.setState({ count: this.state.count + 1 })
console.info(this.state); // 是更新之后的值
})
}
render() {
return (
<Button onClick={this.handleClick}>点击</Button>
)
}
}
结论
从上面的例子可以看出
- 在
合成事件和生命周期中是"异步"的 - 在
原生事件和setTimeout中是同步的
但是,这里说的异步真的是异步吗?那么就用setState和异步事件做比较,看下打印顺序
setState和异步做比较
包含在setTimeout中和异步做比较
handleClick = () => {
setTimeout(() => {
console.log('宏任务触发')
})
Promise.resolve().then(() => {
console.log('微任务触发')
})
setTimeout(() => {
this.setState({
count: this.state.count + 1
}, () => {
console.log('setState变化')
})
})
}
打印顺序: 微任务触发 -> 宏任务触发 -> setState变化
setState直接和异步做比较
handleClick = () => {
setTimeout(() => {
console.log('宏任务触发')
})
Promise.resolve().then(() => {
console.log('微任务触发')
})
this.setState({
count: this.state.count + 1
}, () => {
console.log('setState变化')
})
}
打印顺序:setState变化 -> 微任务触发 -> 宏任务触发
由此可见:setState不是一个异步的任务,只是一个被延迟执行的同步函数
总结
setState是一个伪异步,或者可以称为defer,即延迟执行但本身还在一个事件循环,所以它的执行顺序在同步代码后、异步代码前。为什么会有这种现象?这就要说到react的合成事件了,react的批处理更新也得益于合成事件