1、类组件响应式视图的实现与原理


通过state设置响应式视图,他是组件内私有的,受控于当前组件。

通过state的变化,就可以影响到视图的变化。

class Welcome extends React.Component {
    state = {
        msg: 'hello',
        count: 0
    }
    render(){
        return (
            <div>
                {this.state.msg}, {this.state.count}
            </div>
        );
    }
}
let element = (
    <Welcome />
);

这样就可以在页面中渲染msgcount这两个字段了。

那么怎么才能让state修改后视图跟着发生变化呢,首先不能像Vue那样直接对数据进行修改,在React中是不行的。

React类组件中式通过一个具体的方法**setState()进行state数据的更新,从而触发render()方法的重渲染操作**。

class Welcome extends React.Component {
    state = {
        msg: 'hello',
        count: 0
    }
    handleClick = () => {   
        //this.state.msg = 'hi'  //错误做法,永远不要这样去操作
      	//正确做法:
        this.setState({
            msg: 'hi'
        });
    }
    render(){
        console.log('render');
        return (
            <div>
                <button onClick={this.handleClick}>点击</button>
                {this.state.msg}, {this.state.count}
            </div>
        );
    }
}
let element = (
    <Welcome />
);

state改变视图的原理就是内部会重新调用render()方法,俗称re-render操作。

这里还有注意一点,setState()并不会影响其他state值,内部会完成合并的处理。

2、自动批处理


自动批处理,有助于减少在状态更改时发生的重新渲染次数。

在React18之前也有批处理的,但是在Promise、setTimeout、原生事件中是不起作用的。

实际上自动批处理指的是,同一时机多次调用setState()方法的一种处理机制。

// 当调用两次setState()方法时
handleClick = () => {  
    this.setState({
        msg: 'hi'
    });
    this.setState({
        count: 1
    });
}

这里的代码当点击触发后,虽然调用了两次setState()方法,但是只会触发一次render()方法的重新执行。

那么这就是所谓的自动批处理机制,这样是有助于性能的,减少重新执行的次数。

而且不管在什么时机下,都不会有问题的。

这个在React18版本之前并不是所有的情况都好用的,比如:定时器。

handleClick = () => {  
    setTimeout(()=>{
        this.setState({
            msg: 'hi'
        });
        this.setState({
            count: 1
        });
    }, 2000)
}

上面代码在React18之前的版本中,将会触发两次render()方法。

3、通过ReactDOM.flushSync可以取消批处理操作


默认是自动批处理的,当然也可以改成不是自动批处理的方式,通过ReactDOM.flushSync这个方法。

handleClick = () => {  
    ReactDOM.flushSync(()=>{
        this.setState({
            msg: 'hi'
        });
    })
    ReactDOM.flushSync(()=>{
        this.setState({
            count: 1
        });
    }) 
}

以上代码中的setState()还是会被调用两次。

4、setState()方法是异步处理


既然React18对多次调用采用的是自动批处理机制,那么就说明这个setState()方法是异步的。

所以要注意方法调用完后,我们的state数据并不会立即发生变化,因为state可能会被先执行了:

<script type="text/babel">
  let app = document.querySelector('#app');
  let root = ReactDOM.createRoot(app); 

  class Welcome extends React.Component {
    state = {
      msg: 'hello',
      count: 0
    }
    handleClick = () => {  
      this.setState({
        // 页面显示的是1
        count: this.state.count + 1
      });
      // 打印出的结果是0
      console.log( this.state.count );
    }
    
    render(){
      console.log('render');
      return (
        <div>
          <button onClick={this.handleClick}>点击</button>
          {this.state.msg}, {this.state.count}
        </div>
      );
    }
  }
  let element = (
  <Welcome />
  );

  root.render(element);
</script>

上面的代码里,第一次点击按钮后,打印出的结果是0,页面上显示的是1。说明是异步的。

在异步执行结束后的回调函数里,打印的值是异步之后的值:

handleClick = () => {  
    /* this.setState({
          count: this.state.count + 1
        });
        console.log( this.state.count ); */
    this.setState({
        count: this.state.count + 1
    }, ()=>{  //异步执行结束后的回调函数
        console.log( this.state.count );
    });
}

5、批处理就是合并多个setState(),提供了回调写法

可利用setState()方法的第二个参数来保证数据更新后再去执行。

这里还要注意同样的数据修改只会修改一次:

handleClick = () => {  
  this.setState({
    count: this.state.count + 1
  });
  this.setState({
    count: this.state.count + 1
  });
  this.setState({
    count: this.state.count + 1
  });
}

上面代码里,3个方法里的内容是一样的,会被覆盖掉,最后做批处理时会合并,只会调用一次。

可利用setState()的回调函数写法来保证每一次都能触发:

handleClick = () => {  
    /* this.setState({
          count: this.state.count + 1
        });
        this.setState({
          count: this.state.count + 1
        });
        this.setState({
          count: this.state.count + 1
        }); */
    this.setState((state)=> ({count: state.count + 1}));
    this.setState((state)=> ({count: state.count + 1}));
    this.setState((state)=> ({count: state.count + 1}));
}

这样页面按钮点击一次,count会从0直接变成了3。