关于这个问题,答案无非就两种,要么是同步,要么是异步,哈哈哈哈!!!
好了,不卖关子了,这个问题的答案就是:
setState更新数据状态是异步的,但也有同步的情况(17版本中)
下面让我们来看看到底是怎么回事:
首先,有一个需求:state中num的初始值为1,通过点击按钮实现num加一的操作:
我们先用18的版本来测试一段代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
//react18版本
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
// 在React中更新数据的setState是异步的
class App extends React.Component{
state={num:1}
add = ()=>{
this.setState({num:this.state.num+1})
console.log(this.state.num)//1
}
render(){
return(
<div>
<button onClick={this.add}>{this.state.num}</button>
</div>
)
}
}
const app = ReactDOM.createRoot(document.getElementById('app'))
app.render(<App/>)
</script>
</body>
</html>
运行结果:
结论: 此时界面上变成了2,但打印的结果还是1,由此可以看出setState更新数据状态是异步的
接下来,我们再把需求改一下:在之前的基础上点击一次连续加三次1
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
//react18版本
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class App extends React.Component{
state={num:1}
add = ()=>{
this.setState({num:this.state.num+1})
console.log('1',this.state.num)//1
this.setState({num:this.state.num+1})
console.log('2',this.state.num)//1
this.setState({num:this.state.num+1})
console.log('3',this.state.num)//1
}
render(){
return(
<div>
<button onClick={this.add}>{this.state.num}</button>
</div>
)
}
}
const app = ReactDOM.createRoot(document.getElementById('app'))
app.render(<App/>)
</script>
</body>
</html>
运行结果:
此时页面只更新了一次,控制台打印三次1,说明:setState可以调用多次,会集中一次更新。
那么我们如何得到更新后的数据呢?
通过查找官方文档,setState接收两个参数,第一个:对象/函数 第二个:数据修改并且dom更新后的回调函数
证明:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class App extends React.Component{
state={num:1}
add = ()=>{
this.setState({num:this.state.num+1},()=>{
// state中的数据
console.log('setState回调',this.state.num);//2
// 按钮中的内容
console.log('dom',document.querySelector('button').innerText);//2
})
console.log(this.state.num);//1
}
render(){
return(
<div>
<button onClick={this.add}>{this.state.num}</button>
</div>
)
}
}
const app = ReactDOM.createRoot(document.getElementById('app'))
app.render(<App/>)
</script>
</body>
</html>
运行结果:
此时回调函数中打印的结果以及按钮中的文本也是最新的,说明回调函数是在数据及dom更新完之后随之更新的,那么我们也可以在该回调函数中继续调用setState更新状态
但是,当我们写这种需求时,比如:要在原来状态的基础上进行操作的话,建议使用setState(函数)
官方建议:如果要在原来状态的基础上进行操作的话,建议使用setState(函数)
喔喔喔,原来setState第一个参数也可以是函数呀,那就试试呗
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
// 在React中更新数据的setState是异步的
class App extends React.Component{
state={num:1}
//官方:如果要在原来状态的基础上进行操作的话,建议使用setState(函数)
add = ()=>{
this.setState(prevState=>{
return{
num:prevState.num+1
}
})
this.setState(prevState=>{
return{
num:prevState.num+1
}
},()=>{
console.log(this.state.num);
})
}
render(){
return(
<div>
<button onClick={this.add}>{this.state.num}</button>
</div>
)
}
}
const app = ReactDOM.createRoot(document.getElementById('app'))
app.render(<App/>)
</script>
</body>
</html>
运行结果:
此时,界面和打印结果都是最新的。因此,我们以后在遇到此种场景的时候就需要这样以函数的形式来使用
那么setState更新数据状态都是异步的吗?
这里我们使用react17测试一下
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
//react17版本
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<!-- 在react17中,修改状态可能是同步的 -->
<div id="app"></div>
<script type="text/babel">
class App extends React.Component{
state = {num:1}
add = ()=>{
this.setState({num:this.state.num+1})
console.log(this.state.num);//1
}
render(){
return (
<div>
<button onClick={this.add}>{this.state.num}</button>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('app'))
</script>
</body>
</html>
这里打印结果还是1,说明还是异步的
当使用定时器时:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<!-- 在react17中,修改状态可能是同步的 -->
<div id="app"></div>
<script type="text/babel">
class App extends React.Component{
state = {num:1}
add = ()=>{
// 1.定时器
setTimeout(()=>{
this.setState({num:this.state.num+1})
console.log(this.state.num);//2
})
}
render(){
return (
<div>
<button onClick={this.add}>{this.state.num}</button>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('app'))
</script>
</body>
</html>
运行结果:
由此可见,在定时器的情况下更新状态是同步的
当时用Promise时:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<!-- 在react17中,修改状态可能是同步的 -->
<div id="app"></div>
<script type="text/babel">
class App extends React.Component{
state = {num:1}
add = ()=>{
// 2.Promise中
Promise.resolve().then(()=>{
this.setState({num:this.state.num+1})
console.log(this.state.num);//2
})
}
render(){
return (
<div>
<button onClick={this.add}>{this.state.num}</button>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('app'))
</script>
</body>
</html>
运行结果:
由此可见,在使用Promise的情况下更新状态也是同步的
总结:
React更新数据状态是异步的,但是在react17中可能存在同步的情况(定时器,Promise)
好了,以上就更新状态同步和异步的情况已经做了详细说明,关于setState更新数据状态的答案想必大家都已经理解了吧,下次有人问你的时候,你可以理直气壮地讲给他听啦!!!!