前言记得在8月(2021年)面试的时候,面试官问了我几个setState的问题。现在想来,虽然我回答了上面的问题,但我并不是很理解。我知道setState是为了性能而设计成“异步”的,但是当涉及到源代码解释时,我很茫然;我知道如何让它同步,但是当涉及到真正的代码情况时,我不知道如何下手。毕竟,我正准备把这些概念当面写下来,但并没有真正理解。在了解setState之前,我们问了几个常见的问题。setState是同步的还是异步的?如果是异步的,如何让它同步呢?为什么要这样设计??其中一个基本概念和使用React的想法是UI=f(data)。修改数据带动UI变化,那么怎么修改呢?React提供了一个API——setState(类组件的修改方法)官网介绍:setState()将组件状态的更新加入队列,并通知React该组件及其子组件需要用更新后的状态重新渲染。这是更新UI以响应事件处理程序和处理服务器数据的主要方式。为了获得更好的感知性能,React延迟调用它,然后一次性更新多个组件。React不保证状态更改会立即生效setState()并不总是立即更新组件。它分批推迟更新。这使得在调用setState()之后立即读取this.state成为一种危险。为了消除隐患,请使用componentDidUpdate或者setState的回调函数(setState(updater,callback)),两者都可以保证应用更新后触发,除非shouldComponentUpdate()返回false,否则setState()会一直执行重新-渲染操作。如果使用了可变对象,并且在shouldComponentUpdate()中无法实现条件渲染,那么只在新旧状态不一致时才调用setState()可以避免不必要的重新渲染使用方法setState(updater,[callback])参数一是带形式参数的更新函数:(state,props)=>stateChange//例如//this.setState((state,props)=>{//return{counter:state.counter+props.step};//});setState的第一个参数除了可以接受函数,还可以接受对象类型:setState(stateChange[,callback])//例如:this.setState({count:2})setState的第二个参数是可选的回调函数将在setState完成合并并重新渲染组件后执行。通常,我们推荐使用componentDidUpdate而不是这个方法setState(stateChange[,callback])//例如:this.setState({count:2},()=>{console.log(this.state.count)})和setState回调相比,使用componentDidUpdate有什么优势?stackoverflow有人问,有人答:什么时候setState更适合一致的逻辑批处理更新?当外部代码需要等待状态更新时,比如PromisesetState的特性——批处理如果同一个周期处理多个setState,比如同一个周期设置多次商品数据,相当于:this.setState({count:state.count+1});this.setState({count:state.count+1});this.setState({count:state.count+1});//===Object.assign(count,{quantity:state.quantity+1},{quantity:state.quantity+1},...)setState将覆盖同一周期中最先调用的setState的值。setState(stateChange[,callback])setState((state,props)=>stateChange[,callback])setState必须触发update过程,但不一定会导致render被执行,因为shouldCompomentUpdate可以返回false。批处理引起的问题。问题一:连续使用setState,为什么不能实时更改state.count=0;this.setState({count:state.count+1});this.setState({count:state.count+1});this.setState({count:state.count+1});//state.count===1,因为this.setStat而不是3e方法会进行批处理,后面调用的setState会覆盖同期最先调用的setState的值,如下图:state.count=0;this.setState({count:state.count+2});this.setState({count:state.count+3});this.setState({count:state.count+4});//state.count===4问题2:为什么直接setState而不是this.state.xx=oo?因为setState所做的不仅仅是修改this.state的值,最重要的是它会触发React的更新机制,会进行diff,然后将patch部分更新到真实的dom中,如果你直接设置this.state.xx=oo,state的值确实会改变,但不会驱动React重新渲染。setState可以帮助我们更新view,触发shouldComponentUpdate、render等一系列函数调用。对于批处理,React会将setState的效果放入队列中,在事件结束后生成重新渲染,以尽量减少VirtualDOM和DOM树操作,提高性能。调用setState后,React的生命周期函数会依次执行staticgetDerivedStateFromPropsshouldComponentUpdaterendergetSnapshotBeforeUpdatecomponentDidUpdate。问题三:那为什么会出现异步情况呢?(为什么要这样设计?)因为性能优化。如果每次设置setState都需要更新数据,那么更新过程会经历5个生命周期。一轮生命周期结束后,需要花费大量的时间来比较render函数的结果和更新真实的DOM。因此,将每次调用放在一起一次性处理,可以减少对DOM的操作,提高应用性能。问题四:如何在异步函数中准确获取更新后的状态?通过第二个参数setState(partialState,callback)中的回调onHandleClick(){this.setState({count:this.state.count+1,},()=>{console.log("Callbackafterclick",this.state.count);//最新值});}也可以直接给state传一个函数来显示同步情况this.setState(state=>{console.log("functionmode",state.count);返回{count:state.count+1};});执行原理先了解三种渲染模式:legacy模式:ReactDOM.render(,rootNode)。这是React应用程序当前使用的方式。目前没有移除此模式的计划,但此模式可能不支持新功能阻塞模式:ReactDOM.createBlockingRoot(rootNode).render()。目前是实验性的,作为迁移到并发模式的第一步:ReactDOM.createRoot(rootNode).render()。目前正在实验中,计划在未来稳定后作为React的一种模式开发模式。此模式启用具有不同优先级的所有新功能,并且可以中断更新过程。在legacy模式下,在React的setState函数的实现中,会根据一个变量isBatchingUpdates来判断是直接更新this.state还是放入队列中,后面再说,而isBatchingUpdates默认为false,也就是说setState将同步更新this.state。但是有一个函数batchedUpdates,它会把isBatchingUpdates变为true,当React在调用事件处理函数之前调用这个batchedUpdates的后果就是React控制的事件处理进程setState不会像native那样同步更新this.stateaddEventListener绑定的事件,setTimeout/setInterval会同步走,另外就是React控制的事件处理setState会是异步的,并发方式是异步的,这也是以后React18默认的方式。小结首先,我们总结一下关键知识点setState不会立即改变React组件中state的值。setState触发一次组件更新过程,触发多次重绘第二次setState函数调用的效果会被合并(批处理)。其次,回答文章开头的问题(文章中已经回答了第二个和第三个问题)。setState是同步的还是异步的?代码是同步的,渲染模式是legacy模式,非原生事件,setTimeout/setInterval是异步的;添加事件监听器。绑定原生事件时,setTimeout/setInterval会同步并发方式:异步
