React关于setState

247 阅读3分钟

故事:

最近一直在学习react,感觉setState 在各篇文章中出现的概率还是挺高的,因此学习记录下来关于setState的知识,以达到对react的认识

场景一 合成事件

constructor() {
    super()
    this.state = {
        value: 0
    }
}
handleClick() {
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    console.log(this.state.value)
}
render() {
    return (
        <div className="part-main">
            <button onClick={() => this.handleClick()}>{this.state.value}</button>
        </div>
    )
}

初始化的时候的时候 按钮显示0,点击按钮调用handleClick 输出0 按钮显示1

问题1:为什么输出的不是4呢

直观上调用handleClick时候 value被增加了3次,但是实际上却被增加了1次
实际上react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件。setState在执行过程中是一个很复杂的过程,由 React 控制的事件处理过程 setState 不会同步更新 this.state!,其实归根到底还是为了提升性能,无论你setState执行了多少次,我只渲染一次,这样可以尽可能的提升性能

别人是这样说的,我只是搬运工,方便自己看看

setState 后将传入的 state 放入队列 queue,enqueueUpdate 方法会根据 isBatchingUpdate 标志位判断,若当前已经在更新组件则将直接当前组件放入 dirtyComponents 数组,否则将 isBatchingUpdate 置为 true 并开启一个 "批量更新 (batchedUpdates)" 的事务(transaction)。


场景二 生命周期

constructor() {
    super()
    this.state = {
        value: 0
    }
}
componentDidMount() {
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    console.log(this.state.value)
    //0 输出的还是之前值
}
handleClick() {
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    console.log(this.state.value)

}
render() {
    return (
        <div className="part-main">
            <button onClick={() => this.handleClick()}>{this.state.value}</button>
        </div>
    )
}

初始化的时候的时候 按钮显示1,componentDidMount阶段输出0,点击按钮调用handleClick 输出1 按钮显示2

其实还是和合成事件一样,当componentDidmount执行的时候,react内部并没有更新,执行完componentDidmount后才去commitUpdateQueue更新。这就导致你在componentDidmount中setState完去console.log拿的结果还是更新前的值


场景三 原生事件

constructor() {
    super()
    this.state = {
        value: 0
    }
}
componentDidMount() {
    document.body.addEventListener('click', this.handleClick, false)
}
handleClick = () => {
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    this.setState({
        value: this.state.value + 1
    })
    console.log(this.state.value)
}
render() {
    return (
        <div className="part-main">
            <button>{this.state.value}</button>
        </div>
    )
}

触发事件handleClick 输出4 按钮显示4,卧槽 为啥会这样?

原生事件是指非react合成事件,原生自带的事件监听 addEventListener ,或者也可以用原生js、jq直接 document.querySelector().onclick 这种绑定事件的形式都属于原生事件。  
原生事件中setState的调用栈就比较简单了,因为没有走合成事件的那一大堆,直接触发click事件,到requestWork ,在 requestWork 里由于 expirationTime === Sync 的原因,直接走了 performSyncWork 去更新,并不像合成事件或钩子函数中被return,所以当你在原生事件中setState后,能同步拿到更新后的state值。

场景4 setTimeout

constructor() {
    super()
    this.state = {
        value: 0
    }
}
componentDidMount() {
   setTimeout(() => {
       this.setState({
           value: this.state.value + 1
       })
       this.setState({
           value: this.state.value + 1
       })
       this.setState({
           value: this.state.value + 1
       })
       this.setState({
           value: this.state.value + 1
       })
       console.log(this.state.value)
   },0)
}
handleClick = () => {

}
render() {
    return (
        <div className="part-main">
            <button>{this.state.value}</button>
        </div>
    )
}

setTimeout后 输出4 按钮显示4,卧槽 为啥又是4?

在 setTimeout 中去 setState 并不算是一个单独的场景,它是随着你外层去决定的,因为你可以在合成事件中 setTimeout,可以在钩子函数中 setTimeout,也可以在原生事件setTimeout,但是不管是哪个场景下,基于event loop的模型下,setTimeout 中里去 setState 总能拿到最新的state值。