setState同步OR异步

2,013 阅读5分钟

setState()更新状态的两种写法

  1. setState(updater,[callback])

    updater 为返回stateChange的函数的对象,(state,props) => stateChange

    接收的state和props被保证为最新的

    函数方式:

    1、如果新状态依赖于原状态
    this.setState((state,props) => ({
       count: state.count+1 
    }),()=>{
        // callback
    })
    
  2. setState(stateChange,[callback])

stateChange为对象

callback是可选的回调函数,在状态更新且界面更新后才执行

对象方式:

1、新状态不依赖于原状态 ==>使用对象方式
this.setState({count:this.state.count+1},()=>{
    // callback
})

案例

<body>
  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
  <div id="root"></div>
  <script type="text/babel">
      
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count:0
        }
      }
      test1 = ()=> {
        // 如果新状态依赖于原状态,使用函数方式
        this.setState((state,props)=> ({
          count: state.count+1
        }))
        console.log('test1之后获取count的值:',this.state.count); // 0
      }
      test2 = ()=> {
        // 新状态不依赖于原状态 ==>使用对象方式
        const count = this.state.count + 1;
        this.setState({count});
        console.log('test2之后获取count的值:',this.state.count);
      }
      test3 = ()=> {
        this.setState(state =>({
          count: state.count + 1
        }),()=>{
          console.log('test3-----setState回调函数中count的值:',this.state.count);
        })
      }
      render() {
        return (
          <div>
            <h1>A组件:{this.state.count}</h1>
            <button onClick={this.test1}>测试1</button><br />
            <button onClick={this.test2}>测试2</button><br />  
            <button onClick={this.test3}>测试3</button><br />                
          </div>
        )
      }
    }
    ReactDOM.render(<App />,document.getElementById('root'));
  </script>

</body>

setState的同步与异步

setState()内部是利用的 “事务” 实现异步更新状态

同步

定时器,DOM事件监听回调,Promise

异步

在react控制的回调函数中:生命周期钩子 、 react事件监听的回调

异步的setState() 多次调用,如何处理?

注意:异步情况

  • setState({}): 合并更新一次状态,只调用一次render()更新界面 --- 状态更新和界面更新都合并了
  • setState(fn): 更新多次状态,但只调用一次render更新界面, ----状态更新没有合并但界面更新合并了
  • 先执行setState({}) 对象方式再执行**setState(fn)**函数方式,状态更新没有合并,但界面更新合并了

得到异步更新后的状态数据

在setState()的callback回调函数中

案例

  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
  <div id="root"></div>
  <script type="text/babel">
    
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count:0
        }
      }
      /**
       * react事件监听相关的回调中:- setState()是异步更新状态的
       */ 
      test1 = ()=> {
        console.log('setSate回调函数之前---react事件监听回调',this.state.count);        
        this.setState(state=>({
          count:state.count+1
        }))  
        console.log('setSate回调函数之后---react事件监听回调',this.state.count);
      }
      /**
       * react生命周期钩子中:- setState()是异步更新状态的
       */ 
      componentDidMount() {
        console.log('setSate回调函数之前---生命周期钩子',this.state.count);        
        this.setState(state=>({
          count:state.count+1
        }))  
        console.log('setSate回调函数之后--生命周期钩子',this.state.count); 
      }
      // 同步 (定时器,DOM事件监听回调,Promise)

      test2 = ()=> {
        setTimeout(() => {
          console.log('setSate回调函数之前---定时器',this.state.count);        
          this.setState(state=>({
            count:state.count+1
          }))  
          console.log('setSate回调函数之后---定时器',this.state.count); 
        }, 0);
      }
      test4 = () => {
        const btn4 = this.refs.btn4;
        btn4.onclick = ()=> {
          console.log('setSate回调函数之前---原生DOM',this.state.count);        
          this.setState(state=>({
            count:state.count+1
          }))  
          console.log('setSate回调函数之后---原生DOM',this.state.count); 
        }
      }
      test3 = ()=> {
        Promise.resolve().then(res => {
          console.log('setSate回调函数之前---Promise',this.state.count);        
          this.setState(state=>({
            count:state.count+1
          }))  
          console.log('setSate回调函数之后---Promise',this.state.count); 
        })
      }
      test5 = ()=> {
        console.log('setState 函数方式 - 1 --调用前',this.state.count);
        this.setState(state => ({
          count: state.count+1
        }))
        console.log('setState 函数方式 - 1 --调用后',this.state.count);
        this.setState(state => ({
          count: state.count+1
        }))
        console.log('setState 函数方式 - 2 --调用后',this.state.count);
      }
      test6 = ()=> {
        console.log('setState 对象方式 - 1 --调用前',this.state.count);
        this.setState({count:this.state.count+1});
        console.log('setState 对象方式 - 1 --调用后',this.state.count);
        this.setState({count:this.state.count+1});
        console.log('setState 对象方式 - 2 --调用后',this.state.count);
      }
      test7 = () => {
        console.log('setState 对象方式 - 1 --调用前',this.state.count);
        this.setState({count:this.state.count+1});
        console.log('setState 对象方式 - 1 --调用后',this.state.count);
        this.setState(state => ({
          count: state+1
        }));
        console.log('setState 函数方式 - 1 --调用后',this.state.count);
      }

      render() {
        return (
          <div>
            <h1>A组件:{this.state.count}</h1>
            <button onClick={this.test1}>测试1</button><br />
            <button onClick={this.test2}>测试2</button><br />  
            <button onClick={this.test3}>Promise</button><br />   
            <button ref="btn4" onClick={this.test4}>原生DOM</button><br />                
            <button onClick={this.test5}>setState多次调用---函数方式</button><br />   
            <button onClick={this.test6}>setState多次调用---对象方式</button><br />   
            <button onClick={this.test7}>setState多次调用---对象方式和函数方式</button><br />    
          </div>
        )
      }
    }
    ReactDOM.render(<App />,document.getElementById('root'));
  </script>

setState()常见的面试题

如果上面讲解的内容都能理解的,下面的内容很容易的。🙂

  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
  <div id="root"></div>
  <script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count:0
        }
      }
      /**
       * Promise和定时器相比,promise会先执行
       * setState()执行一次,导致数据发生变化,就会触发render的更新
       */ 

      componentDidMount() {
        this.setState({count:this.state.count+1});
        this.setState({count:this.state.count+1});
        console.log('1----',this.state.count); // 2 --> 0(上面两个会被合并成一个)

        this.setState(state => ({
          count: state.count+1
        }))
        this.setState(state => ({
          count: state.count+1
        }))
        console.log('2-----',this.state.count); // 3 --> 0 (第四次执行,输出结果为3)
        
        setTimeout(() => {
          this.setState({count:this.state.count+1});
          console.log('timeout-1',this.state.count); // 8 ---> 6
          this.setState({count:this.state.count+1});
          console.log('timeout-2',this.state.count); // 10 ---> 7
        }, 0);

        Promise.resolve().then(value=>{
          this.setState({count:this.state.count+1});
          console.log('promise-1',this.state.count); // 5 --> 4
          this.setState({count:this.state.count+1});
          console.log('promise-2',this.state.count); // 7 --> 5
        })

      }
     
      render() {
        const count = this.state.count;
        console.log('render',count); // 1 --- 0 , 4 ---> 3, 6 ---> 4 , 9 ---> 6,11 --> 7
        return (
          <div>
            <h1>A组件:{this.state.count}</h1>            
          </div>
        )
      }
    }
    ReactDOM.render(<App />,document.getElementById('root'));
  </script>