React中setState同步更新策略

React中setState同步更新策略从属于笔者的Web 前端入门与工程实践中的React入门与最佳实践系列总纲系列文章,推荐阅读2016-个人前端之路:工具化与工程化前端

setState 同步更新

咱们在上文中说起,为了提升性能React将setState设置为批次更新,便是异步操做函数,并不能以顺序控制流的方式设置某些事件,咱们也不能依赖于this.state来计算将来状态。典型的譬如咱们但愿在从服务端抓取数据而且渲染到界面以后,再隐藏加载进度条或者外部加载提示:git

componentDidMount() {
    fetch('https://example.com')
        .then((res) => res.json())
        .then(
            (something) => {
                this.setState({ something });
                StatusBar.setNetworkActivityIndicatorVisible(false);
            }
        );
}

由于setState函数并不会阻塞等待状态更新完毕,所以setNetworkActivityIndicatorVisible有可能先于数据渲染完毕就执行。咱们能够选择在componentWillUpdatecomponentDidUpdate这两个生命周期的回调函数中执行setNetworkActivityIndicatorVisible,可是会让代码变得破碎,可读性也很差。实际上在项目开发中咱们更频繁碰见此类问题的场景是以某个变量控制元素可见性:github

this.setState({showForm : !this.state.showForm});

咱们预期的效果是每次事件触发后改变表单的可见性,可是在大型应用程序中若是事件的触发速度快于setState的更新速度,那么咱们的值计算彻底就是错的。本节就是讨论两种方式来保证setState的同步更新。编程

完成回调

setState函数的第二个参数容许传入回调函数,在状态更新完毕后进行调用,譬如:json

this.setState({
      load: !this.state.load,
      count: this.state.count + 1
    }, () => {
      console.log(this.state.count);
      console.log('加载完成')
    });

这里的回调函数用法相信你们很熟悉,就是JavaScript异步编程相关知识,咱们能够引入Promise来封装setState:api

setStateAsync(state) {
    return new Promise((resolve) => {
      this.setState(state, resolve)
    });
  }

setStateAsync 返回的是Promise对象,在调用时咱们可使用Async/Await语法来优化代码风格:网络

async componentDidMount() {
    StatusBar.setNetworkActivityIndicatorVisible(true)
    const res = await fetch('https://api.ipify.org?format=json')
    const {ip} = await res.json()
    await this.setStateAsync({ipAddress: ip})
    StatusBar.setNetworkActivityIndicatorVisible(false)
  }

这里咱们就能够保证在setState渲染完毕以后调用外部状态栏将网络请求状态修改成已结束,整个组件的完整定义为:异步

class AwesomeProject extends Component {
  state = {}
  setStateAsync(state) {
    ...
  }
  async componentDidMount() {
   ...
  }
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          My IP is {this.state.ipAddress || 'Unknown'}
        </Text>
      </View>
    );
  }
}

该组件的执行效果以下所示:
async

传入状态计算函数

除了使用回调函数的方式监听状态更新结果以外,React还容许咱们传入某个状态计算函数而不是对象来做为第一个参数。状态计算函数可以为咱们提供可信赖的组件的State与Props值,即会自动地将咱们的状态更新操做添加到队列中并等待前面的更新完毕后传入最新的状态值:异步编程

this.setState(function(prevState, props){
      return {showForm: !prevState.showForm}
   });

这里咱们以简单的计数器为例,咱们但愿用户点击按钮以后将计数值连加两次,基本的组件为:

class Counter extends React.Component{
  constructor(props){
    super(props);
    this.state = {count : 0} 
    this.incrementCount = this.incrementCount.bind(this)
  }
  incrementCount(){
    ...
  }
  render(){
    return <div>
              <button onClick={this.incrementCount}>Increment</button>
              <div>{this.state.count}</div>
          </div>
  }
}

直观的写法咱们能够连续调用两次setState函数,这边的用法可能看起来有点怪异,不过更多的是为了说明异步更新带来的数据不可预测问题。

incrementCount(){
    this.setState({count : this.state.count + 1}) 
    this.setState({count : this.state.count + 1})
  }

上述代码的效果是每次点击以后计数值只会加1,实际上第二个setState并无等待第一个setState执行完毕就开始执行了,所以其依赖的当前计数值彻底是错的。咱们固然可使用上文说起的setStateAsync来进行同步控制,不过这里咱们使用状态计算函数来保证同步性:

incrementCount(){
   this.setState((prevState, props) => ({
      count: prevState.count + 1
    }));
   this.setState((prevState, props) => ({
      count: prevState.count + 1
    }));
  }

这里的第二个setState传入的prevState值就是第一个setState执行完毕以后的计数值,也顺利保证了连续自增两次。

相关文章
相关标签/搜索