• React 中的重新渲染


    作者:梁瑞锋(晓玉)

    缘起

    React 重新渲染,指的是在类函数中,会重新执行 render 函数,类似 Flutter 中的 build 函数,函数组件中,会重新执行这个函数

    React 组件在组件的状态 state 或者组件的属性 props 改变的时候,会重新渲染,条件简单,但是实际上稍不注意,会引起灾难性的重新渲染

    类组件

    为什么拿类组件先说,怎么说呢,更好理解?还有前几年比较流行的一些常见面试题

    React 中的 setState 什么时候是同步的,什么时候是异步的

    React setState 怎么获取最新的 state

    以下代码的输出值是什么,页面展示是怎么变化的

      test = () => {
       
        // s1 = 1
        const {
        s1 } = this.state;
        this.setState({
        s1: s1 + 1});
        this.setState({
        s1: s1 + 1});
        this.setState({
        s1: s1 + 1});
        console.log(s1)
      };
    
      render() {
       
        return (
          <div>
            <button onClick={
       this.test}>按钮</button>
            <div>{
       this.state.s1}</div>
          </div>
        );
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    看到这些类型的面试问题,熟悉 React 事务机制的你一定能答出来,毕竟不难嘛,哈?你不知道 React 的事务机制?百度|谷歌|360|搜狗|必应 React 事务机制

    React 合成事件

    React 组件触发的事件会被冒泡document(在 react v17 中是 react 挂载的节点,例如 document.querySelector(‘#app’)),然后 React 按照触发路径上收集事件回调,分发事件。

    • 这里是不是突发奇想,如果禁用了,在触发事件的节点,通过原生事件禁止事件冒泡,是不是 React 事件就没法触发了?确实是这样,没法冒泡了,React 都没法收集事件和分发事件了,注意这个冒泡不是 React 合成事件的冒泡。
    • 发散一下还能想到的另外一个点,React ,就算是在合成捕获阶段触发的事件,依旧在原生冒泡事件触发之后
    reactEventCallback = () => {
       
      // s1 s2 s3 都是 1
      const {
        s1, s2, s3 } = this.state;
      this.setState({
        s1: s1 + 1 });
      this.setState({
        s2: s2 + 1 });
      this.setState({
        s3: s3 + 1 });
      console.log('after setState s1:', this.state.s1);
      // 这里依旧输出 1, 页面展示 2,页面仅重新渲染一次
    };
    
    <button
      onClick={
       this.reactEventCallback}
      onClickCapture={
       this.reactEventCallbackCapture}
    >
      React Event
    </button>
    <div>
      S1: {
       s1} S2: {
       s2} S3: {
       s3}
    </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    定时器回调后触发 setState

    定时器回调执行 setState 是同步的,可以在执行 setState 之后直接获取,最新的值,例如下面代码

    timerCallback = () => {
       
      setTimeout(() => {
       
        // s1 s2 s3 都是 1
        const {
        s1, s2, s3 } = this.state;
        this.setState({
        s1: s1 + 1 });
        console.log('after setState s1:', this.state.s1);
        // 输出 2 页面渲染 3 次
        this.setState({
        s2: s2 + 1 });
        this.setState({
        s3: s3 + 1 });
      });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    异步函数后调触发 setState

    异步函数回调执行 setState 是同步的,可以在执行 setState 之后直接获取,最新的值,例如下面代码

    asyncCallback = () => {
       
      Promise.resolve().then(() => {
       
        // s1 s2 s3 都是 1
        const {
        s1, s2, s3 } = this.state;
        this.setState({
        s1: s1 + 1 });
        console.log('after setState s1:', this.state.s1);
        // 输出 2 页面渲染 3 次
        this.setState({
        s2: s2 + 1 });
        this.setState({
        s3: s3 + 1 });
      });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    原生事件触发

    原生事件同样不受 React 事务机制影响,所以 setState 表现也是同步的

    componentDidMount() {
       
      const btn1 = document.getElementById('native-event');
      btn1?.addEventListener('click', this.nativeCallback);
    }
    
    nativeCallback = () => {
       
      // s1 s2 s3 都是 1
      const {
        s1, s2, s3 } = this.state;
      this.setState({
        s1: s1 + 1 });
      console.log('after setState s1:', this.state.s1);
      // 输出 2 页面渲染 3 次
      this.setState({
        s2: s2 + 1 });
      this.setState({
        s3: s3 + 1 });
    };
    
    
    <button id="native-event">Native Event</button>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    setState 修改不参与渲染的属性

    setState 调用就会引起就会组件重新渲染,即使这个状态没有参与页面渲染,所以,请不要把非渲染属性放 state 里面,即使放了 state,也请不要通过 setState 去修改这个状态,直接调用 this.state.xxx = xxx 就好,这种不参与渲染的属性,直接挂在 this 上就好,参考下图

    // s1 s2 s3 为渲染的属性,s4 非渲染属性
    state = {
       
      s1: 1,
      s2: 1,
      s3: 1,
      s4: 1,
    };
    
    s5 = 1;
    
    changeNotUsedState = () => {
       
      const {
        s4 } = this.state;
      this.setState({
        s4: s4 + 1 });
      // 页面会重新渲染
    
      // 页面不会重新渲染
      this.state.s4 = 2;
      this.s5 = 2;
    };
    
    <div>
      S1: {
       s1} S2: {
       s2} S3: {
       s3}
    </div>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    只是调用 setState,页面会不会重新渲染

    几种情况,分别是:

    • 直接调用 setState,无参数
    • setState,新 state 和老 state 完全一致,也就是同样的 state
    sameState = () => {
       
      const {
        s1 } = this.state;
      this.setState({
        s1 });
      // 页面会重新渲染
    };
    
    noParams = () => {
       
      this.setState({
       });
      // 页面会重新渲染
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这两种情况,处理起来和普通的修改状态的 setState 一致,都会引起重新渲染的

    多次渲染的问题

    为什么要提上面这些,仔细看,这里提到了很多次渲染的 3 次,比较契合我们日常写代码的,异步函数回调,毕竟在定时器回调或者给组件绑定原生事件(没事找事是吧?),挺少这么做的吧,但是异步回调就很多了,比如网络请求啥的,改变个 state 还是挺常见的,但是渲染多次,就是不行!不过利用 setState 实际上是传一个新对象合并机制,可以把变化的属性合并在新的对象里面,一次性提交全部变更,就不用调用多次 setState

    asyncCallbackMerge = () => {
       
      Promise.resolve().then(<
    • 1
    • 2
  • 相关阅读:
    Linux常用分析命令
    【教3妹学编程-算法题】 在树上执行操作以后得到的最大分数
    企业微信托管集成语聚AI,做AI智能客服助手,实现精准回答用户问题、创建群组自动化场景
    阿桑的感慨
    Python读取TCP的4字节浮点数
    spring的简单使用(配合Druid操作数据库)
    K8S+ jenkins+gitlub+Harbor实现CI/CD
    MCDF实验4:魔龙的狂舞(从verilog到SV的入门lab4)
    全面解析UDP协议(特点、报文格式、UDP和TCP的区别)
    链表的汇总-习题
  • 原文地址:https://blog.csdn.net/qq_32198115/article/details/127961113