故事:
最近一直在学习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值。