当前位置: 首页 > 科技观察

React中的setState同步更新策略

时间:2023-03-13 00:18:40 科技观察

setState同步更新上文我们提到,为了提高性能,React将setState设置为批量更新,是一个异步操作函数,不能以顺序控制流的方式设置某些事件。我们也不能依赖this.state来计算未来的状态。通常情况下,比如我们希望在从服务器获取数据并渲染到界面后隐藏加载进度条或外部加载提示:componentDidMount(){fetch('https://example.com').then((res)=>res.json()).then((something)=>{this.setState({something});StatusBar.setNetworkActivityIndi??catorVisible(false);});}因为setState函数不会阻塞等待状态待更新,因此setNetworkActivityIndi??catorVisible可能会在数据呈现之前执行。我们可以选择在componentWillUpdate和componentDidUpdate这两个生命周期的回调函数中执行setNetworkActivityIndi??catorVisible,但是这样会使代码断掉,可读性不好。其实我们在项目开发中比较频繁遇到此类问题的场景是通过某个变量来控制元素的可见性:this.setState({showForm:!this.state.showForm});我们预期的效果是每个事件都是在改变表单的可见性之后触发的,但是在大型应用中如果事件的触发速度快于setState的更新速度,那么我们的值计算就完全错误了。本节讨论两种保证setState同步更新的方法。完成回调setState函数的第二个参数允许传入回调函数,在状态更新后调用,例如:this.setState({load:!this.state.load,count:this.state.count+1},()=>{console.log(this.state.count);console.log('加载完成')});这里回调函数的用法相信大家都不陌生,是JavaScript异步编程相关的知识。我们可以引入Promise来封装setState:setStateAsync(state){returnnewPromise((resolve)=>{this.setState(state,resolve)});}setStateAsync返回一个Promise对象,我们可以使用Async/Await语法来优化调用时的代码风格:asynccomponentDidMount(){StatusBar.setNetworkActivityIndi??catorVisible(true)constres=awaitfetch('https://api.ipify.org?format=json')const{ip}=awaitres.json()awaitthis.setStateAsync({ipAddress:ip})StatusBar.setNetworkActivityIndi??catorVisible(false)}这里可以保证setState渲染完成后,可以调用外部状态栏修改网络请求状态为ended。整个组件的完整定义是:classAwesomeProjectextendsComponent{state={}setStateAsync(state){...}asynccomponentDidMount(){...}render(){return(MyIPis{this.state.ipAddress||'Unknown'});}}除了使用回调函数监听状态更新结果外,传入的状态计算函数,React还允许我们传入一个状态计算函数而不是一个对象作为第一个参数。状态计算函数可以为我们提供可靠的组件State和Props值,它会自动将我们的状态更新操作加入到队列中,等待上一次更新传入最新的状态值:this.setState(function(prevState,props){return{showForm:!prevState.showForm}});这里我们以一个简单的计数器为例,我们希望在用户点击按钮后计数值会增加两次。基本组件是:classCounterextendsReact.Component{constructor(props){super(props);this.state={count:0}this.incrementCount=this.incrementCount.bind(this)}incrementCount(){...}render(){return

Increment
{this.state.count}
}}直观的写法,我们可以调用setState函数连续两次。这里的用法可能看起来有点怪异,但更多的是为了说明异步更新带来的不可预知的数据问题。incrementCount(){this.setState({count:this.state.count+1})this.setState({count:this.state.count+1})}上面代码的效果是每次点击后,count值只会加1,实际上第二个setState并没有等到第一个setState执行完才开始执行,所以它依赖的当前count值是完全错误的。当然我们可以使用上面提到的setStateAsync来进行同步控制,但是这里我们使用状态计算函数来保证同步:incrementCount(){this.setState((prevState,props)=>({count:prevState.count+1}));this.setState((prevState,props)=>({count:prevState.count+1}));}这里第二个setState传入的prevState值是第一个setState执行后的计数值,也保证了连续自增两次。【本文为专栏作家“张子雄”原创文章,如需转载,请通过联系作者】点击此处阅读该作者更多好文