我们在一些面试题中经常会遇到一个问题,就是state的变化是同步的还是异步的。一开始只是盲目记得事件触发引起的状态改变是异步的,其他情况下是同步的,没明白其中的道理。下面小编为大家整理一下原因,知道的一定知道为什么。在本文中,我将简要介绍状态在类组件和函数组件中的使用和更新机制。类组件中状态的基本用法在编写类组件时,通过在类中声明状态变量来维护状态,通过setState方法更新存储的状态信息。this.setState(obj[,callback])参数obj可以传两个类型:对象类型:当传入的参数是对象类型时,该对象会合并到state中,更新state的状态。函数类型:当前state和props作为参数,返回值用于与原state合并,更新state状态。在callback回调函数中可以获得最新的状态信息,一些副作用可以作为对状态变化的依赖。state更新机制classCounterextendsReact.Component{constructor(props){super(props);this.state={count:0,hello:"helloworld",};}handleClick=()=>{this.setState({count:this.state.count+1},()=>{console.log("callback1",this.state.count);});console.log("log1",this.state.count);this.setState({count:this.state.count+1},()=>{console.log("callback2",this.state.count);});console.log("log2",this.state.count);this.setState({count:this.state.count+1},()=>{console.log("callback3",this.state.count);});console.log("log3",this.state.count);};render(){console.log("渲染这个",这个);返回({this.state.hello}:{this.state.count});}}打印结果:log10,log20,log30;cb11,cb21,cb31按照最初的想法,执行一次点击操作会执行3次加1的操作,但是从打印结果来看完全没有生效。我们来看看执行过程。其实每次执行setState的时候,state的状态信息还没有更新,每次都使用上次的state状态,所以打印3次的结果都是0。造成这种现象的原因是React会等待所有事件处理器调用setState完成,然后统一更新状态,减少渲染次数。那么是否可以在不重新渲染的情况下更新状态?答案是否定的,原因有以下两个,在React官网都有提到。这会破坏props和state之间的一致性,导致一些难以调试的问题。一些新功能变得不可用。React的issue里面有详细解释:https://github.com/facebook/react/issues/11527那么为什么在事件处理函数中使用setTimeout和Promise会打断这个批量更新导致的异步操作呢?handleClick=()=>{setTimeout(()=>{this.setState({count:this.state.count+1},()=>{console.log("callback1",this.state.count);});console.log("log1",this.state.count);this.setState({count:this.state.count+1},()=>{console.log("callback2",this.state.count);});console.log("log2",this.state.count);this.setState({count:this.state.count+1},()=>{console.log("callback3",this.state.count);});console.log("log3",this.state.count);});};上面代码中,使用setTimeout将代码包裹在handleClick中,我们现在来看打印结果打印结果:callbakc11,log11,callbakc22,log22,callback3,log33.然后是程序执行的流程图现在变成如下形式。每次调用setState函数都会更新状态,然后重新渲染,然后调用setState提供的回调函数。执行回调函数后,代码会继续执行后面的代码。注意:如果React版本是v18,setTimeout和Promise也使用批量更新。如果需要及时更新,有两种方法可以对React进行降级。使用ReactDOM.render(,document.getElementById("root"));,使用该方法相当于降级。那么如何在上面的代码中启用批量更新呢?使用ReactDom提供的unstable_batchedUpdates启用批量更新。不要被前缀unstable影响,既然这个API已经内置到Reactv18中,就不用担心稳定性问题了。Redux官方文档中提供了batch函数,其实就是unstable_batchedUpdates的别名。下面提供redux文档的部分内容:React的unstable_batchedUpdates()API允许将事件循环滴答中的任何React更新一起批处理到单个渲染过程中。React已经在内部将其用于自己的事件处理程序回调。这个API实际上是像ReactDOM和ReactNative这样的渲染器包的一部分,而不是React核心本身。由于React-Redux需要在ReactDOM和ReactNative环境中工作,我们已经注意从正确的渲染器导入这个API为我们自己使用建立时间。我们现在还自己公开重新导出此函数,重命名为batch()。您可以使用它来确保在React外部调度的多个操作只会导致单个渲染更新,就像这样:handleClick=()=>{setTimeout(()=>{ReactDom.unstable_batchedUpdates(()=>{this.setState({count:this.state.count+1},()=>{console.log("callback1",this.state.count);});console.log("log1",this.state.count);this.setState({count:this.state.count+1},()=>{console.log("callback2",this.state.count);});控制台.log("log2",this.state.count);this.setState({count:this.state.count+1},()=>{console.log("callback3",this.state.count);});console.log("log3",this.state.count);}});};函数组件中的state在Reactv16.8中,Hooks的出现让函数组件焕然一新,并使得当时的React开发让读者惊叹不已,从此函数组件成为编写React组件的首选方式state,直接修改state是不允许的,接收的参数类型如下:Non-function:参数会直接替换原来的state,不会合并function:会接收两个参数,state和props,两者都是当前最新的State,返回的结果会更新为state。状态更新机制函数Counter(props){const[count,setCount]=useState(0);console.log("outerCount",计数);consthandleClick=()=>{setCount(count+1);console.log("count1:",计数);setCount((count)=>{console.log('funcCount',count);返回计数+1;});console.log("count2:",count)};returncount:{count};}printresult:count10,count20,funcCount1,outerCount1前两个打印结果都是0,背后的原因很简单的。只有在下一次执行功能组件时,才会更新所有更改的状态。当前函数的执行上下文中的计数是本次渲染函数组件的时间。的初始值。注意:最新状态可以通过函数参数传递给dispathState获取,但不会影响当前函数执行上下文中的状态值。在类组件中,可以通过给setState传递回调函数来监听状态的变化,那么在函数组件中如何检测状态的变化呢?答案是在useEffect的依赖中加入state,当state发生变化时,useEffect中的函数会重新执行。稍微简化一下上面的代码functionCounter(props){const[count,setCount]=useState(0);consthandleClick=()=>{setCount(count+1);};returncount:{count};}假设按钮被点击两次,那么每次执行该功能组件时,流程都会形成一个快照。上图中的每个框图都是一个快照,每个快照都有自己的props、state、eventhandler等函数组件中声明的变量和函数。//首先渲染函数Counter(props){count=0consthandleClick=()=>{setCount(count+1);};计数:{count};}//第二个渲染函数Counter(props){count=1consthandleClick=()=>{setCount(count+1);};count:{count};}//第三个渲染函数Counter(props){count=2consthandleClick=()=>{setCount(count+1);};count:{count};}如何理解每次渲染都有自己的事件处理器?让我们借助Dan提供的示例进行说明。函数Counter(){const[count,setCount]=useState(0);functionhandleAlertClick(){setTimeout(()=>{alert('你点击了:'+count);},3000);}return(
你点击了{count}次
setCount(count+1)}>点击我Showalert);}先点击Showalert按钮,然后在3秒内连续点击Clickme按钮。发现3秒后alert操作依然可以正确显示数字0而不是真实的点击次数。这是因为每次渲染都会生成一个新的handleAlertClick事件处理程序,它会捕获当前渲染的计数。事实上,借助闭包的特性,成功捕获了正确的计数。注意:使用useState提供的dispatch函数来更新state。dispatch函数会浅比较前后两个状态。如果比较相等,则不会更新视图。functionApp(){const[state,dispatchState]=useState({name:'ztq'})consthandleClick=()=>{//点击按钮,视图不更新。state.name='raysuner'dispatchState(state)//直接改变`state`,指向内存中相同的地址。}return(
{state.name}changeName
)}上面的例子改变了state对象的name属性,但是App组件没有更新,原因是state的地址没有变,浅比较前后两个state相等