1、React之setState
发布于 2022年 04月 02日 06:42
在React 16.8版本发布之前,只有类组件可以在构造函数中显示设置state来表示组件的内部状态,然后使用setState()来更新state[reactjs.org/docs/state-…]。
其中:
1)state
只能以object形式表示
2)直接使用this.state.propName = 'xxxx'
,修改propName
的值,不会重新渲染组件
3)出于性能考虑,React会将每个setState()
调用依次放入一个队列,然后再一次渲染中再依次执行相应的setState()
,所以每个setState()
的调用会将该时刻state与props的值以闭包[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures]形式保存,所以多次调用setState修改state对象的某一属性的值,可能会意料之外与期望不一致的结果(官方原文:出于性能考虑,React 可能会把多个 setState()
调用合并成一个调用)。
4)setState
会使用Object.assign(target, ...source)
的方式进行浅合并---基本类型进行值复制,复杂类型进行引用复制。
// index.js
import React from 'react'
import ReactDOM from 'react'
ReactDOM.render(<Counter />, document.getElementById('root))
// Counter.js
import React, { Component } from 'react'
class Counter extends Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}
tick = () => {
this.state.count = this.state.count + 1
console.log(this.state.count)
}
render() {
const { count } = this.state
const style = {marginTop: '120px', marginLeft: '10px'}
return (
<div>
<button onClick={this.tick} style={style}> Click { count } Times</button>
</div>)
}
}
export default Counter
分别点击1次,点击2次,结果如下:
可以看出:
直接使用this.state.propName = 'xxxx'
,可以修改state
属性的值,但组件不会重新渲染
// Counter.js
import React, { Component } from 'react'
class Counter extends Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}
tick = () => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
}
render() {
const { count } = this.state
const style = {marginTop: '120px', marginLeft: '10px'}
return (
<div>
<button onClick={this.tick} style={style}> Click { count } Times</button>
</div>)
}
}
export default Counter
分别点击1次,点击2次,点击3次,结果如下:
可以看出:
使用this.setState
修改state的属性的值,就能使组件重新渲染,但是又会出现一个有趣的现象:打印count
的值总是组件重新渲染前的上一次count
的值,这是由于setState
是异步执行的,console.log(this.state.count)
会在重新渲染前先执行。而这可以通过给this.setState
传入一个回调函数作为第二个参数解决该问题。
// Counter.js
import React, { Component } from 'react'
class Counter extends Component {
constructor(props) {
super(props)
this.state = { count: 0 }
} tick = () => {
this.setState({ count: this.state.count + 1 }, () => {
console.log(this.state.count)
})
}
render() {
const { count } = this.state
const style = {marginTop: '120px', marginLeft: '10px'}
return (
<div>
<button onClick={this.tick} style={style}> Click { count } Times</button>
</div>)
}
}
export default Counter
分别点击1次,点击2次,结果如下:
所以,如果想在state
更新后在执行某些操作,可以传入回调函数来执行这些操作。
但是这样还会有另一个问题:如果在某一事件处理函数中多次调用setState
,而state
中的属性值只会更新一次,注意: 更新一次,但是每一次传入的回调函数会放入相应的事件队列,在完全更新后依次执行。
// Counter.js
import React, { Component } from 'react'
class Counter extends Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}
tick = () => {
this.setState({ count: this.state.count + 1 }, () => {
console.log(this.state.count)
}) }
tickFive = () => {
this.tick()
this.tick()
this.tick()
this.tick()
this.tick()
}
render() {
const { count } = this.state
const style = {marginTop: '120px', marginLeft: '10px'} return (
<div>
<button onClick={this.tickFive} style={style}> Click { count } Times</button>
</div>)
}
}
export default Counter
分别点击1次,点击2次, 结果如下:
可以看到:会出现两个很有意思的现象: 1)尽管在点击1次tickFive
中调用了5次setState
,但是界面却显示只点击了1次,点击2次tickFive
中调用了10次setState
,界面却显示只点击了2次 ...; 2) 5次setState
回调函数里 console.log(this.state.count)
的值都是一样的。为什么呢?这其实是由于JS语言的特性之一----闭包以及setState异步执行综合作用的结果。每一次点击tickFive,里面的tick会调用5次setState
,而tick里每个setState
里传入的参数{count: this.state.count}
里this.state.count
里的值都是该环境里的state
的属性count
的值,即Class Counter的实例state
里的count
值。不好理解?看看下面的一道经典面试题,你就会恍然大悟。(PS:鄙人刚开始学习React,暂时没有阅读react源码,完全是从浏览器对于执行JS的执行,以及JS语言方面在理解,如理解有误方面,欢迎批评与指出。后续有时间会阅读源码,了解更多,并保持更新)
既然如此,那怎么解决呢?
答案就是将传入setState的第一个参数由对象替换成一个updater函数: (state, props) => stateChange
[reactjs.org/docs/react-…]
终极方案
// Counter.js
import React, { Component } from 'react'
class Counter extends Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}
tick = () => {
this.setState(prevState => ({ count: prevState.count + 1 }), () => {
console.log(this.state.count)
})
}
tickFive = () => {
this.tick()
this.tick()
this.tick()
this.tick()
this.tick()
} render() {
const { count } = this.state
const style = {marginTop: '120px', marginLeft: '10px'}
return (
<div>
<button onClick={this.tickFive} style={style}> Click { count } Times</button>
</div>)
}
}
export default Counter
点击1次, 结果如下: