请说出以下代码按钮点击之后输出的值:
import React, { Component } from 'react'
export default class App extends Component {
state = {
num :0
}
handleClick=()=>{
this.setState({
num:this.state.num + 1
})
console.log(this.state.num)
}
render() {
return (
<div>
<h1>
{this.state.num}
</h1>
<p>
<button onClick={this.handleClick}>+</button>
</p>
</div>
)
}
}
按照逻辑,setState一调用便调用render函数,立刻把state的值进行改变,所以你是否认为输出语句的结果是1?
我们看看结果:
我们再次点击,看看输出结果:
看到这里我们猜测setState的执行是异步的,我们在render函数里写上输出语句试试:
import React, { Component } from 'react'
export default class App extends Component {
state = {
num :0
}
handleClick=()=>{
this.setState({
num:this.state.num + 1
})
console.log(this.state.num)
}
render() {
console.log('render')
return (
<div>
<h1>
{this.state.num}
</h1>
<p>
<button onClick={this.handleClick}>+</button>
</p>
</div>
)
}
}
我们点击一下,看看输出结果:
再次点击:
到这里我们可以初步确认setState的执行可能是异步的。
同样的,我们看看下面的代码,猜猜它的输出结果:
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0
}
componentDidMount() {
window.setInterval(() => {
this.setState({
num: this.state.num + 1
})
}, 1000)
console.log(this.state.num)
}
render() {
console.log("render")
return <div>
<h1>{this.state.num}</h1>
</div>
}
}
根据经验,如果setState的执行是异步的,那么这里的执行结果应该是先输出0,再输出render,让我们看看输出结果:
啊这?那如何判断setState是同步还是异步的?
对于某些场景,比如需要在setState之后做某些事,这些事又是依据setState之后的结果,那么这个时候可以借助setState的第二个参数了,这个参数是一个回调函数,在setState执行之后调用。
比如上面的点击例子,如果我们需要在点击之后输出setState最新的值,可以进行以下写法:
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0
}
handleClick=()=>{
this.setState({
num:this.state.num + 1
},()=>{
console.log(this.state.num)
})
}
render() {
console.log('render')
return (
<div>
<h1>
{this.state.num}
</h1>
<p>
<button onClick={this.handleClick}>+</button>
</p>
</div>
)
}
}
我们点击一次之后,看看输出结果:
所以,对于需要在setState之后做点什么事情,可以在其第二个参数callback进行。
说说下面代码的输出结果:
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0
}
handleClick=()=>{
this.setState({
num:this.state.num + 1
})
this.setState({
num:this.state.num + 1
})
this.setState({
num:this.state.num + 1
})
console.log(this.state.num)
}
render() {
console.log('render')
return (
<div>
<h1>
{this.state.num}
</h1>
<p>
<button onClick={this.handleClick}>+</button>
</p>
</div>
)
}
}
如果按照往常的逻辑,我们猜测点击之后,页面会输出3,控制台输出0还有render。
没错,按照setState在事件异步执行的逻辑,我们可以确定控制台的执行结果,可是页面的输出结果却不是3,而是1。
我们换种方式书写代码:
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0
}
handleClick = () => {
this.setState({
num: this.state.num + 1
}, () => { console.log(this.state.num) })
this.setState({
num: this.state.num + 1
}, () => { console.log(this.state.num) })
this.setState({
num: this.state.num + 1
}, () => { console.log(this.state.num) })
}
render() {
console.log('render')
return (
<div>
<h1>
{this.state.num}
</h1>
<p>
<button onClick={this.handleClick}>+</button>
</p>
</div>
)
}
}
控制台输出结果:
在这里我们可以初步判断多个setState运行时,只会合并触发一次render。
假如我们有这样的需求,需要每次setState时用的状态是上一次setState的状态,即setState的同步写法,这个时候你可能想到了用回调函数去处理,即如下代码:
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0
}
handleClick = () => {
this.setState({
num: this.state.num + 1
}, () => {
console.log(this.state.num)
this.setState({
num: this.state.num + 1
}, () => {
console.log(this.state.num)
this.setState({
num: this.state.num + 1
}, () => {
console.log(this.state.num)
})
})
})
}
render() {
console.log('render')
return (
<div>
<h1>
{this.state.num}
</h1>
<p>
<button onClick={this.handleClick}>+</button>
</p>
</div>
)
}
}
看到这里相信很多小伙伴知道答案了,我们看看点击之后输出结果吧。
控制台输出结果:
页面输出结果:
重点来了!!!针对上面这个示例,其实有更加优雅的处理方式,setState第一个参数除了是对象,还可以是一个函数!(该函数有个参数,参数为当前状态,该函数的返回值会覆盖掉之前的状态,并且该函数是异步执行的。)
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0
}
handleClick = () => {
this.setState(state => {
console.log(this.state.num)
return {
num: state.num + 1
}
}
)
this.setState(state => {
console.log(this.state.num)
return {
num: state.num + 1
}
}
)
this.setState(state => {
console.log(this.state.num)
return {
num: state.num + 1
}
}
)
}
render() {
console.log('render')
return (
<div>
<h1>
{this.state.num}
</h1>
<p>
<button onClick={this.handleClick}>+</button>
</p>
</div>
)
}
}
点击之后,我们看看控制台输出结果:
页面输出结果:
这时我们可以明白setState的“同步”写法了,但是对this.state的状态可能不知不解。
我们尝试修改成如下代码:
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0
}
handleClick = () => {
this.setState(state => {
console.log(state.num)
return {
num: state.num + 1
}
}
)
this.setState(state => {
console.log(state.num)
return {
num: state.num + 1
}
}
)
this.setState(state => {
console.log(state.num)
return {
num: state.num + 1
}
}
)
}
render() {
console.log('render')
return (
<div>
<h1>
{this.state.num}
</h1>
<p>
<button onClick={this.handleClick}>+</button>
</p>
</div>
)
}
}
点击之后看看控制台输出结果:
可以看出,函数的参数为上一次return的对象,这种写法类似于中间件,一个传递一个。
划个重点!!!
上面的做法react实际上只会对异步的setState进行这样处理,通常情况下html事件的处理是异步的(事件的处理一般会操作很多数据,包括状态,而事件对state的更改往往是频繁的,将其进行异步处理本质上是一种优化),而比如定时器这种非html事件是同步的。
setState里的回调函数callback运行的时机也会是所有的setState状态更改完成才运行
实际上react对setState的处理会在内部维护一个队列,然后进行对比并一次性render
关于setState源码,博主会在后续的源码研究更新修改,以下是官网部分源码。
让我们看看官方文档里的setState:
Component.prototype.setState = function(partialState, callback) {
//partialState为传入的混入对象,callback为回调函数
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
);//该函数判断传入的值是否为不符合条件,不符合条件会报错
this.updater.enqueueSetState(this, partialState, callback, 'setState');
//updater是在构造函数中定义的一个变量,从调用的方法语义可以看出,调用setState更新的状态会被加入到一个队列中。
};
查看enqueueSetState方法:
enqueueSetState: function(publicInstance, currentPartialState) {
if (queue === null) {
warnNoop(publicInstance, 'setState');
return null;
}
queue.push(currentPartialState); //往队列push状态
},