setState是同步还是异步

209 阅读1分钟

在讨论这个问题之前,先看一个问题

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的批处理更新也得益于合成事件