阅读 275
你真的完全了解setState嘛

你真的完全了解setState嘛

setState到底是同步还是异步?

其实setState可能表现为异步更新也可能表现为同步更新

异步更新情况

生命周期中

state = {
    number:1
};
componentDidMount(){
    this.setState({number:3})
    console.log(this.state.number)  //输出的是更新前的number --> 1
}
复制代码

合成事件中

首先得了解一下什么是合成事件,react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClickonChange这些都是合成事件。

class App extends Component {
  state = { number: 0 }
  increment = () => {
    this.setState({ number: this.state.number + 1 })
    console.log(this.state.number) // 输出的是更新前的number --> 0
  }
  render() {
    return (
      <div onClick={this.increment}>
        {`Counter is: ${this.state.number}`}
      </div>
    )
  }
}
复制代码

那如果我们想拿到更新之后的值应该怎么办呢?主要有以下两种方法

获取setState实时更新过后的值的方法

回调函数

setState提供了一个回调函数供开发者使用,在回调函数中,我们可以实时的获取到更新之后的数据

state = {
    number:1
};
componentDidMount(){
    this.setState({number:3},()=>{
        console.log(this.state.number) // 3
    })
}
复制代码

这个时候大家可以看到控制台打印的数据就是最新的了,我们也就实时的获取到了最新的数据。

componentDidUpdate 生命周期

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0
    }
  }
  render() {
    return (
      <div>
        <h2>当前计数: {this.state.number}</h2>
        <button onClick={e => this.changeText()}>改变数字</button>
      </div>
    )
  }
  componentDidUpdate() {
    // 方式二: 获取异步更新的state
    console.log(this.state.number);
  }
  changeText() {
    this.setState({
      number:this.state.number + 1
    })
  }
}
复制代码

为什么在合成事件和生命周期中会表现为异步更新呢?

归根结底还是因为react框架本身的性能机制所导致的。因为每次调用setState都会触发更新,异步操作是为了提高性能,将多个状态合并一起更新,减少render调用。

试想一下如果在组件中有以下这样一段代码执行:

for ( let i = 0; i < 100; i++ ) {
    this.setState( { num: this.state.num + 1 } );
}
复制代码

如果setState是一个同步执行的机制,那么这个组件会被重新渲染100次,这对性能是一个相当大的消耗。 显然,React也是想到了这个问题,因此对setState做了一些特殊的优化:

React会将多个setState的调用合并为一个来执行,也就是说,当执行setState的时候,state中的数据并不会马上更新,所以打印的值,就会得到更新之前的值。

同步更新情况

setTimeout

state = {
    number:1
};
componentDidMount(){
    setTimeout(()=>{
      this.setState({number:3})
      console.log(this.state.number) // 3
    },0)
}
复制代码

原生事件中

state = {
    number:1
};
componentDidMount() {
    document.body.addEventListener('click', this.changeVal, false);
}
changeVal = () => {
    this.setState({
      number: 3
    })
    console.log(this.state.number) // 3
}
复制代码

为什么上面这两种情况又表现为同步更新呢?

原生事件可以简单理解为因为没有走合成事件的那一大堆东西,跳过了react的机制,直接触发click事件,所以当你在原生事件中setState后,能同步拿到更新后的state值。

setTimeout 可以简单理解为,跳过了react的性能机制,所以也可以同步拿到更新后的state值。

setState中的批量更新

export default class App extends Component {
  state = {
    counter: 0
  }
  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
      </div>
    )
  }
  increment() {
    // 1.setState本身被合并 不会进行累加 只会加1
     this.setState({
       counter: this.state.counter + 1
     });
     this.setState({
       counter: this.state.counter + 1
     });
     this.setState({
       counter: this.state.counter + 1
     });
  }
}
复制代码
  • 直接传递对象的setstate会被合并成一次,只会生效一次,只会加 1.

下面这种写法,就不会被合并,会生效三次。

this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
复制代码

总结

  • setState在合成事件和生命周期函数中是“异步”的,在原生事件和 setTimeout中是同步的

  • 可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。或者在componentDidUpdate生命周期里面拿到更新后的结果

  • 多次setState更新会合并为一次,如果有相同的更新会被覆盖,只会执行一次

限制 state 更新视图

对于类组件如何限制 state 带来的更新作用的呢?

  • pureComponent 可以对 state 和 props 进行浅比较,如果没有发生变化,那么组件不更新。
  • shouldComponentUpdate 生命周期可以通过判断前后 state 变化来决定组件需不需要更新,需要更新返回true,否则返回false。
文章分类
前端
文章标签